forked from j62/ctbrec
1
0
Fork 0

Merge branch 'dev' into manyvids

This commit is contained in:
0xboobface 2020-07-10 18:09:26 +02:00
commit ed0f5476b5
49 changed files with 1782 additions and 1099 deletions

View File

@ -1,3 +1,9 @@
3.8.1
========================
* Fixed recent MFC error
* Added log file rotation
* Fixed a bug with the resolution slider
3.8.0
========================
* Server Settings are now editable in the web-interface

View File

@ -8,7 +8,7 @@
<parent>
<groupId>ctbrec</groupId>
<artifactId>master</artifactId>
<version>3.8.0</version>
<version>3.8.1</version>
<relativePath>../master</relativePath>
</parent>

View File

@ -89,7 +89,6 @@ public class CamrecApplication extends Application {
private Recorder recorder;
private OnlineMonitor onlineMonitor;
static HostServices hostServices;
private SettingsTab settingsTab;
private BorderPane rootPane = new BorderPane();
private HBox statusBar = new HBox();
private Label statusLabel = new Label();
@ -190,8 +189,7 @@ public class CamrecApplication extends Application {
tabPane.getTabs().add(modelsTab);
recordingsTab = new RecordingsTab("Recordings", recorder, config, sites);
tabPane.getTabs().add(recordingsTab);
settingsTab = new SettingsTab(sites, recorder);
tabPane.getTabs().add(settingsTab);
tabPane.getTabs().add(new SettingsTab(sites, recorder));
tabPane.getTabs().add(new NewsTab());
tabPane.getTabs().add(new DonateTabFx());
tabPane.getTabs().add(new HelpTab());
@ -207,6 +205,7 @@ public class CamrecApplication extends Application {
primaryStage.getScene().getStylesheets().add("/ctbrec/ui/ThumbCell.css");
primaryStage.getScene().getStylesheets().add("/ctbrec/ui/controls/SearchBox.css");
primaryStage.getScene().getStylesheets().add("/ctbrec/ui/controls/Popover.css");
primaryStage.getScene().getStylesheets().add("/ctbrec/ui/settings/api/Preferences.css");
primaryStage.getScene().widthProperty().addListener((observable, oldVal, newVal) -> Config.getInstance().getSettings().windowWidth = newVal.intValue());
primaryStage.getScene().heightProperty()
.addListener((observable, oldVal, newVal) -> Config.getInstance().getSettings().windowHeight = newVal.intValue());
@ -258,7 +257,6 @@ public class CamrecApplication extends Application {
public void run() {
modelsTab.saveState();
recordingsTab.saveState();
settingsTab.saveConfig();
onlineMonitor.shutdown();
recorder.shutdown();
for (Site site : sites) {

View File

@ -7,7 +7,7 @@ import ctbrec.Java;
public class Launcher {
private static final transient Logger LOG = LoggerFactory.getLogger(Launcher.class);
private static final Logger LOG = LoggerFactory.getLogger(Launcher.class);
public static void main(String[] args) {
int javaVersion = Java.version();

View File

@ -46,9 +46,10 @@ public abstract class AbstractFileSelectionBox extends HBox {
}
});
Node browse = createBrowseButton();
getChildren().addAll(fileInput, browse);
fileInput.disableProperty().bind(disableProperty());
browse.disableProperty().bind(disableProperty());
fileInput.disableProperty().bind(disableProperty());
fileInput.textProperty().bindBidirectional(fileProperty);
getChildren().addAll(fileInput, browse);
HBox.setHgrow(fileInput, Priority.ALWAYS);
disabledProperty().addListener((obs, oldV, newV) -> {
@ -70,10 +71,12 @@ public abstract class AbstractFileSelectionBox extends HBox {
private ChangeListener<? super String> textListener() {
return (obs, o, n) -> {
String input = fileInput.getText();
if (StringUtil.isBlank(input) && allowEmptyValue) {
fileProperty.set("");
hideValidationHints();
return;
if (StringUtil.isBlank(input)) {
if (allowEmptyValue) {
fileProperty.set("");
hideValidationHints();
return;
}
} else {
File program = new File(input);
setFile(program);
@ -122,6 +125,8 @@ public abstract class AbstractFileSelectionBox extends HBox {
private Button createBrowseButton() {
Button button = new Button("Select");
button.setOnAction(e -> choose());
button.prefHeightProperty().bind(this.heightProperty());
button.prefWidthProperty().set(70);
return button;
}

View File

@ -1,7 +1,8 @@
package ctbrec.ui.controls;
package ctbrec.ui.controls.autocomplete;
import javafx.collections.ObservableList;
import java.util.Optional;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.TextField;
@ -10,12 +11,12 @@ import javafx.scene.input.KeyEvent;
public class AutoFillTextField extends TextField {
private ObservableList<String> suggestions;
private EventHandler<ActionEvent> handler;
private Suggester suggester;
public AutoFillTextField(ObservableList<String> suggestions) {
this.suggestions = suggestions;
addEventHandler(KeyEvent.KEY_RELEASED, (evt) -> {
public AutoFillTextField(Suggester suggester) {
this.suggester = suggester;
addEventHandler(KeyEvent.KEY_RELEASED, evt -> {
if (evt.getCode().isLetterKey() || evt.getCode().isDigitKey()) {
autocomplete(false);
} else if (evt.getCode() == KeyCode.ENTER) {
@ -38,16 +39,19 @@ public class AutoFillTextField extends TextField {
if(oldtext.isEmpty()) {
return;
}
for (String sug : suggestions) {
boolean startsWith = sug.toLowerCase().startsWith(oldtext.toLowerCase());
boolean textMatch = fulltextSearch && sug.toLowerCase().contains(oldtext.toLowerCase());
if(startsWith || textMatch) {
setText(sug);
int pos = oldtext.length();
positionCaret(pos);
selectRange(pos, sug.length());
break;
}
Optional<String> match = null;
if(fulltextSearch) {
match = suggester.fulltext(oldtext);
} else {
match = suggester.startsWith(oldtext);
}
if(match.isPresent()) {
setText(match.get());
int pos = oldtext.length();
positionCaret(pos);
selectRange(pos, match.get().length());
}
}

View File

@ -0,0 +1,37 @@
package ctbrec.ui.controls.autocomplete;
import java.util.Optional;
import javafx.collections.ObservableList;
public class ObservableListSuggester implements Suggester {
private ObservableList<?> suggestions;
public ObservableListSuggester(ObservableList<?> suggestions) {
this.suggestions = suggestions;
}
@Override
public Optional<String> startsWith(String search) {
for (Object sug : suggestions) {
boolean startsWith = sug.toString().toLowerCase().startsWith(search.toLowerCase());
if (startsWith) {
return Optional.of(sug.toString());
}
}
return Optional.empty();
}
@Override
public Optional<String> fulltext(String search) {
for (Object sug : suggestions) {
boolean startsWith = sug.toString().toLowerCase().contains(search.toLowerCase());
if (startsWith) {
return Optional.of(sug.toString());
}
}
return Optional.empty();
}
}

View File

@ -0,0 +1,9 @@
package ctbrec.ui.controls.autocomplete;
import java.util.Optional;
public interface Suggester {
public Optional<String> startsWith(String search);
public Optional<String> fulltext(String search);
}

View File

@ -0,0 +1,5 @@
package ctbrec.ui.controls.autocomplete;
public class Suggestion {
}

View File

@ -1,7 +1,7 @@
package ctbrec.ui.controls.range;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Orientation;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
@ -11,16 +11,17 @@ public class RangeSlider<T extends Number> extends Control {
private static final String DEFAULT_STYLE_CLASS = "rangeslider";
private Range<T> range;
private DoubleProperty low;
private DoubleProperty high;
private ObjectProperty<T> low;
private ObjectProperty<T> high;
private boolean showTickMarks = false;
private boolean showTickLabels = false;
private Orientation orientation = Orientation.HORIZONTAL;
@SuppressWarnings({ "unchecked", "rawtypes" })
public RangeSlider(Range<T> range) {
this.range = range;
low = new SimpleDoubleProperty(getMinimum().doubleValue());
high = new SimpleDoubleProperty(getMaximum().doubleValue());
low = new SimpleObjectProperty(getMinimum());
high = new SimpleObjectProperty(getMaximum());
getStyleClass().setAll(DEFAULT_STYLE_CLASS);
}
@ -34,20 +35,20 @@ public class RangeSlider<T extends Number> extends Control {
return RangeSlider.class.getResource("rangeslider.css").toExternalForm();
}
public DoubleProperty getLow() {
public ObjectProperty<T> getLow() {
return low;
}
public void setLow(T newPosition) {
low.set(newPosition.doubleValue());
low.set(newPosition);
}
public DoubleProperty getHigh() {
public ObjectProperty<T> getHigh() {
return high;
}
public void setHigh(T newPosition) {
this.high.set(newPosition.doubleValue());
this.high.set(newPosition);
}
public T getMinimum() {

View File

@ -5,6 +5,9 @@ import java.util.List;
import com.sun.javafx.scene.control.behavior.BehaviorBase; // NOSONAR
import com.sun.javafx.scene.control.inputmap.InputMap; // NOSONAR
import javafx.scene.Node;
import javafx.scene.input.MouseEvent;
public class RangeSliderBehavior<T extends Number> extends BehaviorBase<RangeSlider<T>> {
private RangeSlider<T> rangeSlider;
@ -12,6 +15,13 @@ public class RangeSliderBehavior<T extends Number> extends BehaviorBase<RangeSli
public RangeSliderBehavior(RangeSlider<T> rangeSlider) {
super(rangeSlider);
this.rangeSlider = rangeSlider;
rangeSlider.addEventFilter(MouseEvent.MOUSE_CLICKED, this::sliderClicked);
}
private void sliderClicked(MouseEvent me) {
Node source = (Node) me.getSource();
double positionPercentage = me.getX() / source.getBoundsInParent().getWidth();
moveClosestThumbTo(positionPercentage);
}
@Override
@ -25,7 +35,12 @@ public class RangeSliderBehavior<T extends Number> extends BehaviorBase<RangeSli
* The mouse position on track with 0.0 being beginning of track and 1.0 being the end
*/
public void lowThumbDragged(double position) {
rangeSlider.setLow(getNewPosition(position));
T newPosition = getNewPosition(position);
T high = getHigh();
if (newPosition.doubleValue() >= high.doubleValue()) {
newPosition = getLow();
}
rangeSlider.setLow(newPosition);
}
/**
@ -33,7 +48,33 @@ public class RangeSliderBehavior<T extends Number> extends BehaviorBase<RangeSli
* The mouse position on track with 0.0 being beginning of track and 1.0 being the end
*/
public void highThumbDragged(double position) {
rangeSlider.setHigh(getNewPosition(position));
T newPosition = getNewPosition(position);
T low = getLow();
if (newPosition.doubleValue() <= low.doubleValue()) {
newPosition = getHigh();
}
rangeSlider.setHigh(newPosition);
}
public void moveClosestThumbTo(double positionPercentage) {
T newPosition = getNewPosition(positionPercentage);
T low = getLow();
T high = getHigh();
double distToLow = Math.abs(low.doubleValue() - newPosition.doubleValue());
double distToHigh = Math.abs(high.doubleValue() - newPosition.doubleValue());
if (distToLow < distToHigh) {
rangeSlider.setLow(newPosition);
} else {
rangeSlider.setHigh(newPosition);
}
}
private T getLow() {
return rangeSlider.getRange().getTicks().get(rangeSlider.getLow().get().intValue());
}
private T getHigh() {
return rangeSlider.getRange().getTicks().get(rangeSlider.getHigh().get().intValue());
}
/**
@ -50,4 +91,5 @@ public class RangeSliderBehavior<T extends Number> extends BehaviorBase<RangeSli
index = Math.min(ticks.size() - 1, Math.max(0, index));
return ticks.get(index);
}
}

View File

@ -31,8 +31,8 @@ public class RangeSliderSkin extends SkinBase<RangeSlider<?>> {
this.behavior = behavior;
initTrack();
initThumbs(thumbRange);
registerChangeListener(control.getLow(), (obsVal) -> getSkinnable().requestLayout());
registerChangeListener(control.getHigh(), (obsVal) -> getSkinnable().requestLayout());
registerChangeListener(control.getLow(), obsVal -> getSkinnable().requestLayout());
registerChangeListener(control.getHigh(), obsVal -> getSkinnable().requestLayout());
}
private void initThumbs(ThumbRange t) {
@ -42,10 +42,8 @@ public class RangeSliderSkin extends SkinBase<RangeSlider<?>> {
t.low.setOnMousePressed(me -> {
preDragThumbPoint = t.low.localToParent(me.getX(), me.getY());
preDragPos = (getSkinnable().getLow().doubleValue() - getSkinnable().getMinimum().doubleValue()) / (getMaxMinusMinNoZero());
preDragPos = (getSkinnable().getLow().get().doubleValue() - getSkinnable().getMinimum().doubleValue()) / (getMaxMinusMinNoZero());
});
t.low.setOnMouseDragged(me -> {
Point2D cur = t.low.localToParent(me.getX(), me.getY());
double dragPos = (isHorizontal()) ? cur.getX() - preDragThumbPoint.getX() : -(cur.getY() - preDragThumbPoint.getY());
@ -54,17 +52,14 @@ public class RangeSliderSkin extends SkinBase<RangeSlider<?>> {
t.high.setOnMousePressed(me -> {
preDragThumbPoint = t.high.localToParent(me.getX(), me.getY());
preDragPos = (getSkinnable().getHigh().doubleValue() - getSkinnable().getMinimum().doubleValue()) / (getMaxMinusMinNoZero());
preDragPos = (getSkinnable().getHigh().get().doubleValue() - getSkinnable().getMinimum().doubleValue()) / (getMaxMinusMinNoZero());
});
t.high.setOnMouseDragged(me -> {
boolean orientation = getSkinnable().getOrientation() == Orientation.HORIZONTAL;
double trackLen = orientation ? track.getWidth() : track.getHeight();
Point2D cur = t.high.localToParent(me.getX(), me.getY());
double dragPos = getSkinnable().getOrientation() != Orientation.HORIZONTAL ? -(cur.getY() - preDragThumbPoint.getY()) : cur.getX() - preDragThumbPoint.getX();
behavior.highThumbDragged(preDragPos + dragPos / trackLen);
});
}
@ -165,8 +160,8 @@ public class RangeSliderSkin extends SkinBase<RangeSlider<?>> {
private void positionThumbs() {
RangeSlider<?> s = getSkinnable();
double lxl = trackStart + (trackLength * ((s.getLow().doubleValue() - s.getMinimum().doubleValue()) / (getMaxMinusMinNoZero())) - thumbWidth / 2D);
double lxh = trackStart + (trackLength * ((s.getHigh().doubleValue() - s.getMinimum().doubleValue()) / (getMaxMinusMinNoZero())) - thumbWidth / 2D);
double lxl = trackStart + (trackLength * ((s.getLow().get().doubleValue() - s.getMinimum().doubleValue()) / (getMaxMinusMinNoZero())) - thumbWidth / 2D);
double lxh = trackStart + (trackLength * ((s.getHigh().get().doubleValue() - s.getMinimum().doubleValue()) / (getMaxMinusMinNoZero())) - thumbWidth / 2D);
double ly = lowThumbPos;
if (thumbRange != null) {
@ -208,7 +203,6 @@ public class RangeSliderSkin extends SkinBase<RangeSlider<?>> {
if (isHorizontal()) {
if (showTickMarks) {
double w = Math.max(140, tickLine.prefWidth(-1));
System.err.println("computePrefWidth " + w);
return w;
} else {
return 140;

View File

@ -50,9 +50,7 @@ import javafx.scene.control.ScrollPane;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.Separator;
import javafx.scene.control.TextField;
import javafx.scene.control.TitledPane;
import javafx.scene.image.Image;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
@ -61,8 +59,8 @@ import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.Window;
public class ActionSettingsPanel extends TitledPane {
private static final transient Logger LOG = LoggerFactory.getLogger(ActionSettingsPanel.class);
public class ActionSettingsPanel extends GridPane {
private static final Logger LOG = LoggerFactory.getLogger(ActionSettingsPanel.class);
private ListView<EventHandlerConfiguration> actionTable;
private TextField name = new TextField();
@ -80,11 +78,8 @@ public class ActionSettingsPanel extends TitledPane {
private Recorder recorder;
public ActionSettingsPanel(SettingsTab settingsTab, Recorder recorder) {
public ActionSettingsPanel(Recorder recorder) {
this.recorder = recorder;
setText("Events & Actions");
setExpanded(true);
setCollapsible(false);
createGui();
loadEventHandlers();
}
@ -94,15 +89,21 @@ public class ActionSettingsPanel extends TitledPane {
}
private void createGui() {
BorderPane mainLayout = new BorderPane();
setContent(mainLayout);
setHgap(10);
setVgap(10);
setPadding(new Insets(20, 10, 10, 10));
Label headline = new Label("Events & Actions");
headline.getStyleClass().add("settings-group-label");
add(headline, 0, 0);
actionTable = createActionTable();
ScrollPane scrollPane = new ScrollPane(actionTable);
scrollPane.setFitToHeight(true);
scrollPane.setFitToWidth(true);
scrollPane.setStyle("-fx-background-color: -fx-background");
mainLayout.setCenter(scrollPane);
add(scrollPane, 0, 1);
GridPane.setHgrow(scrollPane, Priority.ALWAYS);
Button add = new Button("Add");
add.setOnAction(this::add);
@ -110,15 +111,10 @@ public class ActionSettingsPanel extends TitledPane {
delete.setOnAction(this::delete);
delete.setDisable(true);
HBox buttons = new HBox(5, add, delete);
mainLayout.setBottom(buttons);
BorderPane.setMargin(buttons, new Insets(5, 0, 0, 0));
buttons.setStyle("-fx-background-color: -fx-background"); // workaround so that the buttons don't shrink
add(buttons, 0, 2);
actionTable.getSelectionModel().getSelectedItems().addListener(new ListChangeListener<EventHandlerConfiguration>() {
@Override
public void onChanged(Change<? extends EventHandlerConfiguration> change) {
delete.setDisable(change.getList().isEmpty());
}
});
actionTable.getSelectionModel().getSelectedItems().addListener((ListChangeListener<EventHandlerConfiguration>) change -> delete.setDisable(change.getList().isEmpty()));
}
private void add(ActionEvent evt) {
@ -235,9 +231,7 @@ public class ActionSettingsPanel extends TitledPane {
event.getItems().clear();
event.getItems().add(Event.Type.MODEL_STATUS_CHANGED);
event.getItems().add(Event.Type.RECORDING_STATUS_CHANGED);
event.setOnAction(evt -> {
modelState.setVisible(event.getSelectionModel().getSelectedItem() == Event.Type.MODEL_STATUS_CHANGED);
});
event.setOnAction(evt -> modelState.setVisible(event.getSelectionModel().getSelectedItem() == Event.Type.MODEL_STATUS_CHANGED));
event.getSelectionModel().select(Event.Type.MODEL_STATUS_CHANGED);
layout.add(event, 1, row++);
@ -254,7 +248,7 @@ public class ActionSettingsPanel extends TitledPane {
Label l = new Label("Models");
layout.add(l, 0, row);
modelSelectionPane = new ListSelectionPane<Model>(recorder.getModels(), Collections.emptyList());
modelSelectionPane = new ListSelectionPane<>(recorder.getModels(), Collections.emptyList());
layout.add(modelSelectionPane, 1, row++);
GridPane.setValignment(l, VPos.TOP);
GridPane.setHgrow(modelSelectionPane, Priority.ALWAYS);
@ -289,7 +283,6 @@ public class ActionSettingsPanel extends TitledPane {
private ListView<EventHandlerConfiguration> createActionTable() {
ListView<EventHandlerConfiguration> view = new ListView<>();
view.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
view.setPrefSize(300, 200);
return view;
}

View File

@ -1,6 +1,9 @@
package ctbrec.ui.settings;
import java.io.IOException;
import ctbrec.Config;
import ctbrec.ui.controls.Dialogs;
import javafx.scene.control.Button;
import javafx.scene.control.ColorPicker;
import javafx.scene.control.Tooltip;
@ -14,41 +17,49 @@ public class ColorSettingsPane extends HBox {
ColorPicker accentColor = new ColorPicker();
Button reset = new Button("Reset");
Pane foobar = new Pane();
private Config config;
public ColorSettingsPane(SettingsTab settingsTab) {
public ColorSettingsPane(Config config) {
super(5);
this.config = config;
getChildren().add(baseColor);
getChildren().add(accentColor);
getChildren().add(reset);
baseColor.setValue(Color.web(Config.getInstance().getSettings().colorBase));
baseColor.setValue(Color.web(config.getSettings().colorBase));
baseColor.setTooltip(new Tooltip("Base Color"));
baseColor.setMaxSize(44, 25);
accentColor.setValue(Color.web(Config.getInstance().getSettings().colorAccent));
accentColor.setValue(Color.web(config.getSettings().colorAccent));
accentColor.setTooltip(new Tooltip("Accent Color"));
accentColor.setMaxSize(44, 25);
reset.setMinSize(60, 25);
reset.setMaxSize(60, 25);
baseColor.setOnAction(evt -> {
Config.getInstance().getSettings().colorBase = toWeb(baseColor.getValue());
settingsTab.showRestartRequired();
settingsTab.saveConfig();
config.getSettings().colorBase = toWeb(baseColor.getValue());
save();
});
accentColor.setOnAction(evt -> {
Config.getInstance().getSettings().colorAccent = toWeb(accentColor.getValue());
settingsTab.showRestartRequired();
settingsTab.saveConfig();
config.getSettings().colorAccent = toWeb(accentColor.getValue());
save();
});
reset.setOnAction(evt -> {
baseColor.setValue(Color.WHITE);
Config.getInstance().getSettings().colorBase = toWeb(Color.WHITE);
config.getSettings().colorBase = toWeb(Color.WHITE);
accentColor.setValue(Color.WHITE);
Config.getInstance().getSettings().colorAccent = toWeb(Color.WHITE);
settingsTab.showRestartRequired();
settingsTab.saveConfig();
config.getSettings().colorAccent = toWeb(Color.WHITE);
save();
});
}
private void save() {
try {
config.save();
} catch (IOException e) {
Dialogs.showError(getScene(), "Save Settings", "Couldn't save color settings", e);
}
}
private String toWeb(Color value) {
StringBuilder sb = new StringBuilder("#");
sb.append(toHex((int) (value.getRed() * 255)));

View File

@ -0,0 +1,285 @@
package ctbrec.ui.settings;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Config;
import ctbrec.Settings;
import ctbrec.StringUtil;
import ctbrec.ui.controls.DirectorySelectionBox;
import ctbrec.ui.controls.ProgramSelectionBox;
import ctbrec.ui.controls.range.DiscreteRange;
import ctbrec.ui.controls.range.RangeSlider;
import ctbrec.ui.settings.api.ExclusiveSelectionProperty;
import ctbrec.ui.settings.api.Preferences;
import ctbrec.ui.settings.api.PreferencesStorage;
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 javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ListProperty;
import javafx.beans.property.LongProperty;
import javafx.beans.property.Property;
import javafx.beans.property.StringProperty;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TextField;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.HBox;
import javafx.util.converter.NumberStringConverter;
public class CtbrecPreferencesStorage implements PreferencesStorage {
private static final Logger LOG = LoggerFactory.getLogger(CtbrecPreferencesStorage.class);
public static final String PATTERN_NOT_A_DIGIT = "[^\\d]";
public static final String COULDNT_SAVE_MSG = "Couldn't save config setting";
private Config config;
private Settings settings;
public CtbrecPreferencesStorage(Config config) {
this.config = config;
this.settings = config.getSettings();
}
@Override
public void save(Preferences preferences) throws IOException {
throw new RuntimeException("not implemented");
}
@Override
public void load(Preferences preferences) {
throw new RuntimeException("not implemented");
}
@Override
public Node createGui(Setting setting) throws Exception {
Property<?> prop = setting.getProperty();
if (prop instanceof ExclusiveSelectionProperty) {
return createRadioGroup(setting);
} else if (prop instanceof SimpleRangeProperty) {
return createRangeSlider(setting);
} else if (prop instanceof SimpleDirectoryProperty) {
return createDirectorySelector(setting);
} else if (prop instanceof SimpleFileProperty) {
return createFileSelector(setting);
} else if (prop instanceof IntegerProperty) {
return createIntegerProperty(setting);
} else if (prop instanceof LongProperty) {
return createLongProperty(setting);
} else if (prop instanceof BooleanProperty) {
return createBooleanProperty(setting);
} else if (prop instanceof ListProperty) {
return createComboBox(setting);
} else if (prop instanceof StringProperty) {
return createStringProperty(setting);
} else {
return new Label("Unsupported Type for key " + setting.getKey() + ": " + setting.getProperty());
}
}
private Node createRadioGroup(Setting setting) {
ExclusiveSelectionProperty prop = (ExclusiveSelectionProperty) setting.getProperty();
ToggleGroup toggleGroup = new ToggleGroup();
RadioButton optionA = new RadioButton(prop.getOptionA());
optionA.setSelected(prop.getValue());
optionA.setToggleGroup(toggleGroup);
RadioButton optionB = new RadioButton(prop.getOptionB());
optionB.setSelected(!optionA.isSelected());
optionB.setToggleGroup(toggleGroup);
optionA.selectedProperty().bindBidirectional(prop);
prop.addListener((obs, oldV, newV) -> saveValue(() -> {
Field field = Settings.class.getField(setting.getKey());
field.set(settings, newV);
config.save();
}));
HBox row = new HBox();
row.getChildren().addAll(optionA, optionB);
HBox.setMargin(optionA, new Insets(5));
HBox.setMargin(optionB, new Insets(5));
return row;
}
@SuppressWarnings("unchecked")
private Node createRangeSlider(Setting setting) {
SimpleRangeProperty<Integer> rangeProperty = (SimpleRangeProperty<Integer>) setting.getProperty();
DiscreteRange<Integer> range = (DiscreteRange<Integer>) rangeProperty.getRange();
List<Integer> labels = (List<Integer>) range.getLabels();
List<Integer> values = range.getTicks();
RangeSlider<Integer> resolutionRange = new RangeSlider<>(rangeProperty.getRange());
resolutionRange.setShowTickMarks(true);
resolutionRange.setShowTickLabels(true);
int lowValue = getRangeSliderValue(values, labels, Config.getInstance().getSettings().minimumResolution);
resolutionRange.setLow(lowValue >= 0 ? lowValue : values.get(0));
int highValue = getRangeSliderValue(values, labels, Config.getInstance().getSettings().maximumResolution);
resolutionRange.setHigh(highValue >= 0 ? highValue : values.get(values.size() - 1));
resolutionRange.getLow().addListener((obs, o, n) -> saveValue(() -> {
int newV = labels.get(n.intValue());
Field field = Settings.class.getField(rangeProperty.getLowKey());
field.set(settings, newV);
config.save();
}));
resolutionRange.getHigh().addListener((obs, o, n) -> saveValue(() -> {
int newV = labels.get(n.intValue());
Field field = Settings.class.getField(rangeProperty.getHighKey());
field.set(settings, newV);
config.save();
}));
return resolutionRange;
}
private int getRangeSliderValue(List<Integer> values, List<Integer> labels, int value) {
for (int i = 0; i < labels.size(); i++) {
int label = labels.get(i).intValue();
if(label == value) {
return values.get(i);
}
}
return -1;
}
private Node createFileSelector(Setting setting) {
ProgramSelectionBox programSelector = new ProgramSelectionBox("");
programSelector.fileProperty().addListener((obs, o, n) -> saveValue(() -> {
String path = n;
Field field = Settings.class.getField(setting.getKey());
String oldValue = (String) field.get(settings);
if (!Objects.equals(path, oldValue)) {
field.set(settings, path);
config.save();
}
}));
StringProperty property = (StringProperty) setting.getProperty();
programSelector.fileProperty().bindBidirectional(property);
return programSelector;
}
private Node createDirectorySelector(Setting setting) {
DirectorySelectionBox directorySelector = new DirectorySelectionBox("");
directorySelector.prefWidth(400);
directorySelector.fileProperty().addListener((obs, o, n) -> saveValue(() -> {
String path = n;
Field field = Settings.class.getField(setting.getKey());
String oldValue = (String) field.get(settings);
if (!Objects.equals(path, oldValue)) {
field.set(settings, path);
config.save();
}
}));
StringProperty property = (StringProperty) setting.getProperty();
directorySelector.fileProperty().bindBidirectional(property);
return directorySelector;
}
private Node createStringProperty(Setting setting) {
TextField ctrl = new TextField();
ctrl.textProperty().addListener((obs, oldV, newV) -> saveValue(() -> {
Field field = Settings.class.getField(setting.getKey());
field.set(settings, newV);
config.save();
}));
StringProperty prop = (StringProperty) setting.getProperty();
ctrl.textProperty().bindBidirectional(prop);
return ctrl;
}
@SuppressWarnings("unchecked")
private Node createIntegerProperty(Setting setting) {
TextField ctrl = new TextField();
ctrl.textProperty().addListener((obs, oldV, newV) -> saveValue(() -> {
if (!newV.matches("\\d*")) {
ctrl.setText(newV.replaceAll(PATTERN_NOT_A_DIGIT, ""));
}
if (!ctrl.getText().isEmpty()) {
Field field = Settings.class.getField(setting.getKey());
field.set(settings, Integer.parseInt(ctrl.getText()));
config.save();
}
}));
Property<Number> prop = setting.getProperty();
ctrl.textProperty().bindBidirectional(prop, new NumberStringConverter());
return ctrl;
}
@SuppressWarnings("unchecked")
private Node createLongProperty(Setting setting) {
TextField ctrl = new TextField();
ctrl.textProperty().addListener((obs, oldV, newV) -> saveValue(() -> {
if (!newV.matches("\\d*")) {
ctrl.setText(newV.replaceAll(PATTERN_NOT_A_DIGIT, ""));
}
if (!ctrl.getText().isEmpty()) {
long value = Long.parseLong(ctrl.getText());
if (setting.getConverter() != null) {
value = (long) setting.getConverter().convertFrom(value);
}
Field field = Settings.class.getField(setting.getKey());
field.set(settings, value);
config.save();
}
}));
Property<Number> prop = setting.getProperty();
ctrl.textProperty().bindBidirectional(prop, new NumberStringConverter());
return ctrl;
}
private Node createBooleanProperty(Setting setting) {
CheckBox ctrl = new CheckBox();
ctrl.selectedProperty().addListener((obs, oldV, newV) -> saveValue(() -> {
Field field = Settings.class.getField(setting.getKey());
field.set(settings, newV);
config.save();
}));
BooleanProperty prop = (BooleanProperty) setting.getProperty();
ctrl.selectedProperty().bindBidirectional(prop);
return ctrl;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private Node createComboBox(Setting setting) throws NoSuchFieldException, IllegalAccessException {
ListProperty<?> listProp = (ListProperty<?>) setting.getProperty();
ComboBox<Object> comboBox = new ComboBox(listProp);
Field field = Settings.class.getField(setting.getKey());
Object value = field.get(settings);
if (StringUtil.isNotBlank(value.toString())) {
if (setting.getConverter() != null) {
comboBox.getSelectionModel().select(setting.getConverter().convertTo(value));
} else {
comboBox.getSelectionModel().select(value);
}
}
comboBox.valueProperty().addListener((obs, oldV, newV) -> saveValue(() -> {
if (setting.getConverter() != null) {
field.set(settings, setting.getConverter().convertFrom(newV));
} else {
field.set(settings, newV);
}
config.save();
}));
return comboBox;
}
private void saveValue(Exec exe) {
try {
exe.run();
} catch (Exception e) {
LOG.error(COULDNT_SAVE_MSG, e);
}
}
@FunctionalInterface
private interface Exec {
public void run() throws Exception;
}
}

View File

@ -0,0 +1,160 @@
package ctbrec.ui.settings;
import static javafx.scene.control.ButtonType.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Collections;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.Moshi;
import com.squareup.moshi.Types;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.io.ModelJsonAdapter;
import ctbrec.sites.Site;
import ctbrec.ui.AutosizeAlert;
import ctbrec.ui.controls.Dialogs;
import javafx.geometry.Insets;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.SelectionMode;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.stage.FileChooser;
public class IgnoreList extends GridPane {
private static final Logger LOG = LoggerFactory.getLogger(IgnoreList.class);
private ListView<Model> ignoreListView;
private List<Site> sites;
public IgnoreList(List<Site> sites) {
this.sites = sites;
createGui();
loadIgnoredModels();
}
private void createGui() {
setHgap(10);
setVgap(10);
setPadding(new Insets(20, 10, 10, 10));
Label headline = new Label("Ignore List");
headline.getStyleClass().add("settings-group-label");
add(headline, 0, 0);
ignoreListView = new ListView<>();
ignoreListView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
ignoreListView.addEventHandler(KeyEvent.KEY_PRESSED, event -> {
if (event.getCode() == KeyCode.DELETE) {
removeSelectedModels();
}
});
add(ignoreListView, 0, 1);
GridPane.setHgrow(ignoreListView, Priority.ALWAYS);
Button remove = new Button("Remove");
remove.setOnAction(evt -> removeSelectedModels());
Button exportIgnoreList = new Button("Export");
exportIgnoreList.setOnAction(e -> exportIgnoreList());
Button importIgnoreList = new Button("Import");
importIgnoreList.setOnAction(e -> importIgnoreList());
HBox buttons = new HBox(10, remove, exportIgnoreList, importIgnoreList);
add(buttons, 0, 2);
buttons.setStyle("-fx-background-color: -fx-background"); // workaround so that the buttons don't shrink
}
private void removeSelectedModels() {
List<Model> selectedModels = ignoreListView.getSelectionModel().getSelectedItems();
if (selectedModels.isEmpty()) {
return;
} else {
Config.getInstance().getSettings().modelsIgnored.removeAll(selectedModels);
ignoreListView.getItems().removeAll(selectedModels);
LOG.debug(Config.getInstance().getSettings().modelsIgnored.toString());
try {
Config.getInstance().save();
} catch (IOException e) {
LOG.warn("Couldn't save config", e);
}
}
}
private void loadIgnoredModels() {
List<Model> ignored = Config.getInstance().getSettings().modelsIgnored;
ignoreListView.getItems().clear();
ignoreListView.getItems().addAll(ignored);
Collections.sort(ignoreListView.getItems());
}
public void refresh() {
loadIgnoredModels();
}
private void exportIgnoreList() {
FileChooser chooser = new FileChooser();
chooser.setTitle("Export ignore list");
chooser.setInitialFileName("ctbrec-ignorelist.json");
File file = chooser.showSaveDialog(null);
if (file != null) {
Moshi moshi = new Moshi.Builder().add(Model.class, new ModelJsonAdapter(sites)).build();
Type modelListType = Types.newParameterizedType(List.class, Model.class);
JsonAdapter<List<Model>> adapter = moshi.adapter(modelListType);
adapter = adapter.indent(" ");
try (FileOutputStream out = new FileOutputStream(file)) {
String json = adapter.toJson(Config.getInstance().getSettings().modelsIgnored);
out.write(json.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
Dialogs.showError(getScene(), "Couldn't export ignore list", e.getLocalizedMessage(), e);
}
}
}
private void importIgnoreList() {
FileChooser chooser = new FileChooser();
chooser.setTitle("Import ignore list");
File file = chooser.showOpenDialog(null);
if (file != null) {
Moshi moshi = new Moshi.Builder().add(Model.class, new ModelJsonAdapter(sites)).build();
Type modelListType = Types.newParameterizedType(List.class, Model.class);
JsonAdapter<List<Model>> adapter = moshi.adapter(modelListType);
try {
byte[] fileContent = Files.readAllBytes(file.toPath());
List<Model> ignoredModels = adapter.fromJson(new String(fileContent, StandardCharsets.UTF_8));
boolean confirmed = true;
if (!Config.getInstance().getSettings().modelsIgnored.isEmpty()) {
String msg = "This will replace the existing ignore list! Continue?";
AutosizeAlert confirm = new AutosizeAlert(AlertType.CONFIRMATION, msg, getScene(), YES, NO);
confirm.setTitle("Import ignore list");
confirm.setHeaderText("Overwrite ignore list");
confirm.showAndWait();
confirmed = confirm.getResult() == ButtonType.YES;
}
if (confirmed) {
Config.getInstance().getSettings().modelsIgnored = ignoredModels;
refresh();
}
} catch (IOException e) {
Dialogs.showError(getScene(), "Couldn't import ignore list", e.getLocalizedMessage(), e);
}
}
}
}

View File

@ -1,96 +0,0 @@
package ctbrec.ui.settings;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.ui.controls.Dialogs;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.ListView;
import javafx.scene.control.SelectionMode;
import javafx.scene.image.Image;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.GridPane;
import javafx.stage.Modality;
import javafx.stage.Stage;
public class IgnoreListDialog extends Dialog<Void> {
private static final Logger LOG = LoggerFactory.getLogger(IgnoreListDialog.class);
private Scene parent;
private ListView<Model> ignoreList;
public IgnoreListDialog(Scene parent) {
this.parent = parent;
createGui();
loadIgnoredModels();
}
private void createGui() {
setTitle("Ignore List");
getDialogPane().getButtonTypes().addAll(ButtonType.OK);
initModality(Modality.APPLICATION_MODAL);
setResizable(true);
InputStream icon = Dialogs.class.getResourceAsStream("/icon.png");
Stage stage = (Stage) getDialogPane().getScene().getWindow();
stage.getIcons().add(new Image(icon));
if (parent != null) {
stage.getScene().getStylesheets().addAll(parent.getStylesheets());
}
GridPane grid = new GridPane();
grid.setHgap(10);
grid.setVgap(10);
grid.setPadding(new Insets(20, 150, 10, 10));
ignoreList = new ListView<>();
ignoreList.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
ignoreList.addEventHandler(KeyEvent.KEY_PRESSED, event -> {
if (event.getCode() == KeyCode.DELETE) {
removeSelectedModels();
}
});
grid.add(ignoreList, 0, 0);
Button remove = new Button("Remove");
remove.setOnAction(evt -> removeSelectedModels());
grid.add(remove, 0, 1);
getDialogPane().setContent(grid);
setResizable(true);
}
private void removeSelectedModels() {
List<Model> selectedModels = ignoreList.getSelectionModel().getSelectedItems();
if (selectedModels.isEmpty()) {
return;
} else {
Config.getInstance().getSettings().modelsIgnored.removeAll(selectedModels);
ignoreList.getItems().removeAll(selectedModels);
LOG.debug(Config.getInstance().getSettings().modelsIgnored.toString());
try {
Config.getInstance().save();
} catch (IOException e) {
LOG.warn("Couldn't save config", e);
}
}
}
private void loadIgnoredModels() {
List<Model> ignored = Config.getInstance().getSettings().modelsIgnored;
ignoreList.getItems().addAll(ignored);
Collections.sort(ignoreList.getItems());
}
}

View File

@ -1,101 +0,0 @@
package ctbrec.ui.settings;
import java.io.IOException;
import java.io.InputStream;
import ctbrec.Config;
import ctbrec.Settings;
import ctbrec.ui.controls.Dialogs;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.stage.Modality;
import javafx.stage.Stage;
public class PlayerSettingsDialog extends Dialog<Exception> {
private Scene parent;
private Config config;
private Settings settings;
private TextField playerParams;
private TextField maxResolution;
public PlayerSettingsDialog(Scene parent, Config config) {
this.parent = parent;
this.config = config;
this.settings = config.getSettings();
initGui();
}
private void initGui() {
setTitle("Player Settings");
setHeaderText("Player Settings");
getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
initModality(Modality.APPLICATION_MODAL);
setResizable(true);
InputStream icon = Dialogs.class.getResourceAsStream("/icon.png");
Stage stage = (Stage) getDialogPane().getScene().getWindow();
stage.getIcons().add(new Image(icon));
if (parent != null) {
stage.getScene().getStylesheets().addAll(parent.getStylesheets());
}
GridPane grid = new GridPane();
grid.setHgap(10);
grid.setVgap(10);
grid.setPadding(new Insets(20, 150, 10, 10));
grid.add(new Label("Start parameters"), 0, 0);
playerParams = new TextField(settings.mediaPlayerParams);
grid.add(playerParams, 1, 0);
getDialogPane().setContent(grid);
GridPane.setFillWidth(playerParams, true);
GridPane.setHgrow(playerParams, Priority.ALWAYS);
Label l = new Label("Maximum resolution (0 = unlimited)");
grid.add(l, 0, 1);
maxResolution = new TextField(Integer.toString(settings.maximumResolutionPlayer));
Tooltip tt = new Tooltip("video height, e.g. 720 or 1080");
l.setTooltip(tt);
maxResolution.setTooltip(tt);
grid.add(maxResolution, 1, 1);
getDialogPane().setContent(grid);
GridPane.setFillWidth(maxResolution, true);
GridPane.setHgrow(maxResolution, Priority.ALWAYS);
Platform.runLater(playerParams::requestFocus);
setResultConverter(dialogButton -> {
try {
if (dialogButton == ButtonType.OK) {
saveSettings();
}
return null;
} catch (IOException e) {
return e;
}
});
}
public void saveSettings() throws IOException {
settings.mediaPlayerParams = playerParams.getText();
String res = maxResolution.getText();
if (res.matches("\\d+")) {
int newRes = Integer.parseInt(maxResolution.getText());
if (newRes != Config.getInstance().getSettings().maximumResolutionPlayer) {
settings.maximumResolutionPlayer = newRes;
}
}
config.save();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,153 @@
package ctbrec.ui.settings.api;
import static java.util.Optional.*;
import java.util.function.Supplier;
import ctbrec.StringUtil;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.Pane;
public class Category {
protected String name;
protected Group[] groups;
protected Category[] subCategories;
protected Node gui;
private Category(String name, Group... groups) {
this.name = name;
this.groups = groups;
}
public Category(String name, Category[] subCategories) {
this.name = name;
this.subCategories = subCategories;
groups = new Group[0];
}
public Category(String name, Node gui) {
this.name = name;
this.gui = gui;
groups = new Group[0];
}
public static Category of(String name, Setting... settings) {
return new Category(name, Group.of(settings));
}
public static Category of(String name, Group... groups) {
return new Category(name, groups);
}
public static Category of(String name, Category... subCategories) {
return new Category(name, subCategories);
}
public static Category of(String name, Node gui) {
return new Category(name, gui);
}
String getName() {
return name;
}
Group[] getGroups() {
return groups;
}
Category[] getSubCategories() {
return subCategories;
}
boolean hasGroups() {
return groups != null && groups.length > 0 && !groups[0].isDefault();
}
boolean hasSubCategories() {
return subCategories != null && subCategories.length > 0;
}
Node getGuiOrElse(Supplier<Node> guiFactory) {
if (gui == null) {
gui = guiFactory.get();
}
return gui;
}
@Override
public String toString() {
return name;
}
public boolean contains(String filter) {
if (StringUtil.isBlank(filter)) {
return true;
}
String q = filter.toLowerCase().trim();
if(hasGroups() || hasSubCategories()) {
return name.toLowerCase().contains(q)
| groupsContains(q)
| subCategoriesContains(q);
} else {
return name.toLowerCase().contains(q)
| guiContains(q);
}
}
private boolean subCategoriesContains(String filter) {
boolean contains = false;
if (subCategories != null) {
for (Category category : subCategories) {
if (category.contains(filter)) {
contains = true;
}
}
}
return contains;
}
private boolean groupsContains(String filter) {
boolean contains = false;
if (groups != null) {
for (Group group : groups) {
if (group.contains(filter)) {
contains = true;
}
}
}
return contains;
}
private boolean guiContains(String filter) {
if (gui != null) {
return nodeContains(gui, filter);
}
return false;
}
private boolean nodeContains(Node node, String filter) {
boolean contains = false;
if (node instanceof Pane) {
Pane pane = (Pane) node;
for (Node child : pane.getChildren()) {
contains |= nodeContains(child, filter);
}
}
if (node instanceof Label) {
Label lbl = (Label) node;
contains |= lbl.getText().toLowerCase().contains(filter);
contains |= ofNullable(lbl.getTooltip()).map(Tooltip::getText).orElse("").toLowerCase().contains(filter);
}
if (node instanceof Control) {
contains |= ofNullable(((Control) node).getTooltip()).map(Tooltip::getText).orElse("").toLowerCase().contains(filter);
}
contains |= node.toString().toLowerCase().contains(filter);
return contains;
}
}

View File

@ -0,0 +1,23 @@
package ctbrec.ui.settings.api;
import javafx.beans.property.SimpleBooleanProperty;
public class ExclusiveSelectionProperty extends SimpleBooleanProperty {
private String optionA;
private String optionB;
public ExclusiveSelectionProperty(Object bean, String name, boolean value, String optionA, String optionB) {
super(bean, name, value);
this.optionA = optionA;
this.optionB = optionB;
}
public String getOptionA() {
return optionA;
}
public String getOptionB() {
return optionB;
}
}

View File

@ -0,0 +1,19 @@
package ctbrec.ui.settings.api;
public class GigabytesConverter implements ValueConverter {
private static final int ONE_GIB_IN_BYTES = 1024 * 1024 * 1024;
@Override
public Object convertTo(Object a) {
long input = (long) a;
return input / ONE_GIB_IN_BYTES;
}
@Override
public Object convertFrom(Object b) {
long spaceLeftInGiB = (long) b;
return spaceLeftInGiB * ONE_GIB_IN_BYTES;
}
}

View File

@ -0,0 +1,51 @@
package ctbrec.ui.settings.api;
import java.util.Objects;
public class Group {
public static final String DEFAULT = "default";
private String name;
private Setting[] settings;
private Group(String name, Setting...settings) {
this.name = name;
this.settings = settings;
}
public static Group of(Setting...settings) {
return new Group(DEFAULT, settings);
}
public static Group of(String name, Setting...settings) {
return new Group(name, settings);
}
String getName() {
return name;
}
Setting[] getSettings() {
return settings;
}
boolean isDefault() {
return Objects.equals(name, DEFAULT);
}
public boolean contains(String filter) {
return name.toLowerCase().contains(filter) | settingsContain(filter);
}
private boolean settingsContain(String filter) {
boolean contains = false;
if (settings != null) {
for (Setting setting : settings) {
if (setting.contains(filter)) {
contains = true;
}
}
}
return contains;
}
}

View File

@ -0,0 +1,89 @@
package ctbrec.ui.settings.api;
import static java.util.Optional.*;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Label;
import javafx.scene.control.TextInputControl;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.Pane;
class HighlightingSupport {
private static final String CSS_HIGHLIGHT_CLASS = "setting-highlighted";
private HighlightingSupport() {}
static void highlightMatches(Category cat, String filter) {
Node node = cat.getGuiOrElse(Label::new);
highlightMatchess(node, filter);
if(cat.hasSubCategories()) {
for (Category sub : cat.getSubCategories()) {
highlightMatches(sub, filter);
}
}
}
static void highlightMatchess(Node node, String filter) {
boolean contains = false;
if (node instanceof Pane) {
Pane pane = (Pane) node;
for (Node child : pane.getChildren()) {
highlightMatchess(child, filter);
}
}
if (node instanceof Label) {
Label lbl = (Label) node;
contains |= lbl.getText().toLowerCase().contains(filter);
contains |= ofNullable(lbl.getTooltip()).map(Tooltip::getText).orElse("").toLowerCase().contains(filter);
contains |= labelControlContains(lbl, filter);
if (contains) {
if (!node.getStyleClass().contains(CSS_HIGHLIGHT_CLASS)) {
node.getStyleClass().add(CSS_HIGHLIGHT_CLASS);
}
} else {
node.getStyleClass().remove(CSS_HIGHLIGHT_CLASS);
}
}
}
private static boolean labelControlContains(Label lbl, String filter) {
boolean contains = false;
if (lbl.labelForProperty().get() != null) {
Node labeledNode = lbl.labelForProperty().get();
contains |= labeledNode.toString().toLowerCase().contains(filter);
if (labeledNode instanceof Control) {
contains |= ofNullable(((Control) labeledNode).getTooltip()).map(Tooltip::getText).orElse("").toLowerCase().contains(filter);
}
if (labeledNode instanceof TextInputControl) {
contains |= ofNullable(((TextInputControl) labeledNode).getText()).orElse("").toLowerCase().contains(filter);
}
}
return contains;
}
static void removeHighlights(Category cat) {
Node node = cat.getGuiOrElse(Label::new);
removeHighlights(node);
if(cat.hasSubCategories()) {
for (Category sub : cat.getSubCategories()) {
removeHighlights(sub);
}
}
}
static void removeHighlights(Node gui) {
if (gui != null) {
if (gui instanceof Pane) {
Pane p = (Pane) gui;
for (Node n : p.getChildren()) {
removeHighlights(n);
}
}
gui.getStyleClass().remove(CSS_HIGHLIGHT_CLASS);
}
}
}

View File

@ -0,0 +1,8 @@
.settings-group-label {
-fx-font-size: 1.6em;
}
.setting-highlighted {
-fx-border-color: -fx-accent;
-fx-border-width: 3;
}

View File

@ -0,0 +1,240 @@
package ctbrec.ui.settings.api;
import static java.util.Optional.*;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.ui.controls.SearchBox;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
public class Preferences {
private static final Logger LOG = LoggerFactory.getLogger(Preferences.class);
private Category[] categories;
private TreeView<Category> categoryTree;
private Preferences(PreferencesStorage preferencesStorage, Category...categories) {
this.categories = categories;
for (Category category : categories) {
assignPreferencesStorage(category, preferencesStorage);
}
}
private void assignPreferencesStorage(Category cat, PreferencesStorage preferencesStorage) {
if(cat.hasSubCategories()) {
for (Category sub : cat.getSubCategories()) {
assignPreferencesStorage(sub, preferencesStorage);
}
} else {
for (Group group : cat.getGroups()) {
for (Setting setting : group.getSettings()) {
setting.setPreferencesStorage(preferencesStorage);
}
}
}
}
public static Preferences of(PreferencesStorage preferencesStorage, Category... categories) {
return new Preferences(preferencesStorage, categories);
}
public void save() {
throw new RuntimeException("save not implemented");
}
Category[] getCategories() {
return categories;
}
public Node getView() {
SearchBox search = new SearchBox(true);
search.textProperty().addListener(this::filterTree);
TreeItem<Category> categoryTreeItems = createCategoryTree(categories, new TreeItem<>(), null);
categoryTree = new TreeView<>(categoryTreeItems);
categoryTree.showRootProperty().set(false);
VBox leftSide = new VBox(search, categoryTree);
VBox.setVgrow(categoryTree, Priority.ALWAYS);
VBox.setMargin(search, new Insets(2));
VBox.setMargin(categoryTree, new Insets(2));
BorderPane main = new BorderPane();
main.setLeft(leftSide);
main.setCenter(new Label("Center"));
BorderPane.setMargin(leftSide, new Insets(2));
categoryTree.getSelectionModel().selectedItemProperty().addListener((obs, oldV, newV) -> {
if (newV != null) {
Category cat = newV.getValue();
Node gui = cat.getGuiOrElse(() -> createGui(cat));
BorderPane.setMargin(gui, new Insets(10));
main.setCenter(gui);
}
});
categoryTree.getSelectionModel().select(0);
return main;
}
private void filterTree(ObservableValue<? extends String> obs, String oldV, String newV) {
String q = ofNullable(newV).orElse("").toLowerCase().trim();
TreeItem<Category> filteredCategoryTree = createCategoryTree(categories, new TreeItem<>(), q);
categoryTree.setRoot(filteredCategoryTree);
expandAll(categoryTree.getRoot());
TreeItem<Category> parent = categoryTree.getRoot();
while (!parent.getChildren().isEmpty()) {
parent = parent.getChildren().get(0);
categoryTree.getSelectionModel().select(parent);
}
for (Category category : categories) {
if (q.length() > 2) {
HighlightingSupport.highlightMatches(category, q);
} else {
HighlightingSupport.removeHighlights(category);
}
}
}
private void expandAll(TreeItem<Category> treeItem) {
treeItem.setExpanded(true);
for (TreeItem<Category> child : treeItem.getChildren()) {
expandAll(child);
}
}
private Node createGui(Category cat) {
try {
if (cat.hasSubCategories()) {
return new Label(cat.getName());
} else if(cat.hasGroups()) {
return createPaneWithGroups(cat);
} else {
return createGrid(cat.getGroups()[0].getSettings());
}
} catch(Exception e) {
LOG.error("Error creating the GUI", e);
return new Label(e.getLocalizedMessage());
}
}
private Node createPaneWithGroups(Category cat) throws Exception {
VBox pane = new VBox();
for (Group grp : cat.getGroups()) {
Label groupLabel = new Label(grp.getName());
groupLabel.getStyleClass().add("settings-group-label");
VBox.setMargin(groupLabel, new Insets(20, 0, 10, 20));
pane.getChildren().add(groupLabel);
Node parameterGrid = createGrid(grp.getSettings());
pane.getChildren().add(parameterGrid);
VBox.setMargin(parameterGrid, new Insets(0, 0, 0, 40));
}
return pane;
}
private Node createGrid(Setting[] settings) throws Exception {
GridPane pane = new GridPane();
int row = 0;
for (Setting setting : settings) {
Node node = setting.getGui();
Label label = new Label(setting.getName());
label.setMinHeight(34);
label.labelForProperty().set(node);
label.setTooltip(new Tooltip(setting.getName()));
pane.addRow(row++, label, node);
GridPane.setVgrow(label, Priority.ALWAYS);
GridPane.setValignment(label, VPos.CENTER);
GridPane.setMargin(node, new Insets(5, 0, 5, 10));
GridPane.setValignment(node, VPos.CENTER);
GridPane.setHgrow(node, Priority.ALWAYS);
}
return pane;
}
/**
* Creates a tree of the given categories. Filters out categories, which don't match the filter
* @param filter may be null
*/
private TreeItem<Category> createCategoryTree(Category[] categories, TreeItem<Category> parent, String filter) {
for (Category category : categories) {
TreeItem<Category> child = new TreeItem<>(category);
if (category.hasSubCategories()) {
createCategoryTree(category.getSubCategories(), child, filter);
if (!child.getChildren().isEmpty()) {
parent.getChildren().add(child);
}
} else if(category.contains(filter)) {
parent.getChildren().add(child);
}
}
return parent;
}
public void expandTree() {
expandAll(categoryTree.getRoot());
}
public void traverse(Consumer<Setting> visitor) {
for (Category category : categories) {
visit(category, visitor);
}
}
private void visit(Category cat, Consumer<Setting> visitor) {
if (cat.hasGroups()) {
for (Group group : cat.getGroups()) {
for (Setting setting : group.getSettings()) {
visitor.accept(setting);
}
}
}
if (cat.hasSubCategories()) {
for (Category subcat : cat.getSubCategories()) {
visit(subcat, visitor);
}
}
}
public Optional<Setting> getSetting(String key) {
SettingSearchVisitor search = new SettingSearchVisitor(key);
traverse(search);
return search.getResult();
}
private class SettingSearchVisitor implements Consumer<Setting> {
Optional<Setting> result = Optional.empty();
private String key;
public SettingSearchVisitor(String key) {
this.key = key;
}
@Override
public void accept(Setting s) {
if (Objects.equals(key, ofNullable(s.getKey()).orElse(""))) {
result = Optional.of(s);
}
}
public Optional<Setting> getResult() {
return result;
}
}
}

View File

@ -0,0 +1,13 @@
package ctbrec.ui.settings.api;
import java.io.IOException;
import javafx.scene.Node;
public interface PreferencesStorage {
void save(Preferences preferences) throws IOException;
void load(Preferences preferences);
Node createGui(Setting setting) throws Exception;
}

View File

@ -0,0 +1,110 @@
package ctbrec.ui.settings.api;
import static java.util.Optional.*;
import ctbrec.StringUtil;
import javafx.beans.property.Property;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Tooltip;
public class Setting {
private String name;
private String tooltip;
private Property<?> property;
private Node gui;
private PreferencesStorage preferencesStorage;
private boolean needsRestart = false;
private ValueConverter converter;
protected Setting(String name, Property<?> property) {
this.name = name;
this.property = property;
}
protected Setting(String name, Node gui) {
this.name = name;
this.gui = gui;
}
public static Setting of(String name, Property<?> property) {
return new Setting(name, property);
}
public static Setting of(String name, Property<?> property, String tooltip) {
Setting setting = new Setting(name, property);
setting.tooltip = tooltip;
return setting;
}
public static Setting of(String name, Node gui) {
Setting setting = new Setting(name, gui);
return setting;
}
public String getName() {
return name;
}
public String getKey() {
if (getProperty() == null) {
return "";
} else {
String key = getProperty().getName();
if (StringUtil.isBlank(key)) {
throw new IllegalStateException("Name for property of setting [" + name + "] is null");
}
return key;
}
}
public String getTooltip() {
return tooltip;
}
public Setting needsRestart() {
needsRestart = true;
return this;
}
public boolean doesNeedRestart() {
return needsRestart;
}
@SuppressWarnings("rawtypes")
public Property getProperty() {
return property;
}
public Node getGui() throws Exception {
if (gui == null) {
gui = preferencesStorage.createGui(this);
if (gui instanceof Control && StringUtil.isNotBlank(tooltip)) {
Control control = (Control) gui;
control.setTooltip(new Tooltip(tooltip));
}
}
return gui;
}
public void setPreferencesStorage(PreferencesStorage preferencesStorage) {
this.preferencesStorage = preferencesStorage;
}
public boolean contains(String filter) {
boolean contains = name.toLowerCase().contains(filter)
|| ofNullable(tooltip).orElse("").toLowerCase().contains(filter)
|| ofNullable(property).map(Property::getValue).map(Object::toString).orElse("").toLowerCase().contains(filter);
return contains;
}
public Setting converter(ValueConverter converter) {
this.converter = converter;
return this;
}
public ValueConverter getConverter() {
return converter;
}
}

View File

@ -0,0 +1,11 @@
package ctbrec.ui.settings.api;
import javafx.beans.property.SimpleStringProperty;
public class SimpleDirectoryProperty extends SimpleStringProperty {
public SimpleDirectoryProperty(Object bean, String name, String initialValue) {
super(bean, name, initialValue);
}
}

View File

@ -0,0 +1,11 @@
package ctbrec.ui.settings.api;
import javafx.beans.property.SimpleStringProperty;
public class SimpleFileProperty extends SimpleStringProperty {
public SimpleFileProperty(Object bean, String name, String initialValue) {
super(bean, name, initialValue);
}
}

View File

@ -0,0 +1,43 @@
package ctbrec.ui.settings.api;
import ctbrec.ui.controls.range.Range;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
public class SimpleRangeProperty<T> extends SimpleObjectProperty<T> {
private Range<T> range;
private SimpleObjectProperty<T> lowProperty;
private SimpleObjectProperty<T> highProperty;
private String lowKey;
private String highKey;
public SimpleRangeProperty(Range<T> range, String lowKey, String highKey, T low, T high) {
super(null, lowKey);
this.range = range;
this.lowKey = lowKey;
this.highKey = highKey;
lowProperty = new SimpleObjectProperty<>(low);
highProperty = new SimpleObjectProperty<>(high);
}
public Range<T> getRange() {
return range;
}
public ObjectProperty<T> lowProperty() {
return lowProperty;
}
public ObjectProperty<T> highProperty() {
return highProperty;
}
public String getLowKey() {
return lowKey;
}
public String getHighKey() {
return highKey;
}
}

View File

@ -0,0 +1,7 @@
package ctbrec.ui.settings.api;
public interface ValueConverter {
Object convertTo(Object a);
Object convertFrom(Object b);
}

View File

@ -29,11 +29,9 @@ public class MyFreeCamsConfigUI extends AbstractConfigUI {
GridPane layout = SettingsTab.createGridLayout();
Settings settings = Config.getInstance().getSettings();
Label l = new Label("Active");
layout.add(l, 0, row);
CheckBox enabled = new CheckBox();
enabled.setSelected(!settings.disabledSites.contains(myFreeCams.getName()));
enabled.setOnAction((e) -> {
enabled.setOnAction(e -> {
if(enabled.isSelected()) {
settings.disabledSites.remove(myFreeCams.getName());
} else {
@ -42,10 +40,13 @@ public class MyFreeCamsConfigUI extends AbstractConfigUI {
save();
});
GridPane.setMargin(enabled, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
Label l = new Label("Active");
l.labelForProperty().set(enabled);
layout.add(l, 0, row);
layout.add(enabled, 1, row++);
layout.add(new Label("MyFreeCams User"), 0, row);
TextField username = new TextField(Config.getInstance().getSettings().mfcUsername);
username.setId("mfcUsername");
username.setPrefWidth(300);
username.textProperty().addListener((ob, o, n) -> {
if(!n.equals(Config.getInstance().getSettings().mfcUsername)) {
@ -57,9 +58,11 @@ public class MyFreeCamsConfigUI extends AbstractConfigUI {
GridPane.setFillWidth(username, true);
GridPane.setHgrow(username, Priority.ALWAYS);
GridPane.setColumnSpan(username, 2);
l = new Label("MyFreeCams User");
l.labelForProperty().set(username);
layout.add(l, 0, row);
layout.add(username, 1, row++);
layout.add(new Label("MyFreeCams Password"), 0, row);
PasswordField password = new PasswordField();
password.setText(Config.getInstance().getSettings().mfcPassword);
password.textProperty().addListener((ob, o, n) -> {
@ -72,13 +75,15 @@ public class MyFreeCamsConfigUI extends AbstractConfigUI {
GridPane.setFillWidth(password, true);
GridPane.setHgrow(password, Priority.ALWAYS);
GridPane.setColumnSpan(password, 2);
l = new Label("MyFreeCams Password");
l.labelForProperty().set(password);
layout.add(l, 0, row);
layout.add(password, 1, row++);
Button createAccount = new Button("Create new Account");
createAccount.setOnAction(e -> DesktopIntegration.open(myFreeCams.getAffiliateLink()));
layout.add(createAccount, 1, row++);
layout.add(new Label("MyFreeCams Base URL"), 0, row);
TextField baseUrl = new TextField();
baseUrl.setText(Config.getInstance().getSettings().mfcBaseUrl);
baseUrl.textProperty().addListener((ob, o, n) -> {
@ -88,6 +93,9 @@ public class MyFreeCamsConfigUI extends AbstractConfigUI {
GridPane.setFillWidth(baseUrl, true);
GridPane.setHgrow(baseUrl, Priority.ALWAYS);
GridPane.setColumnSpan(baseUrl, 2);
l = new Label("MyFreeCams Base URL");
l.labelForProperty().set(baseUrl);
layout.add(l, 0, row);
layout.add(baseUrl, 1, row);
GridPane.setColumnSpan(createAccount, 2);

View File

@ -39,10 +39,11 @@ import ctbrec.ui.action.PauseAction;
import ctbrec.ui.action.PlayAction;
import ctbrec.ui.action.ResumeAction;
import ctbrec.ui.action.StopRecordingAction;
import ctbrec.ui.controls.AutoFillTextField;
import ctbrec.ui.controls.DateTimeCellFactory;
import ctbrec.ui.controls.Dialogs;
import ctbrec.ui.controls.SearchBox;
import ctbrec.ui.controls.autocomplete.AutoFillTextField;
import ctbrec.ui.controls.autocomplete.ObservableListSuggester;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringPropertyBase;
@ -254,7 +255,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
modelLabel.setPadding(new Insets(5, 0, 0, 0));
ObservableList<String> suggestions = FXCollections.observableArrayList();
sites.forEach(site -> suggestions.add(site.getClass().getSimpleName()));
model = new AutoFillTextField(suggestions);
model = new AutoFillTextField(new ObservableListSuggester(suggestions));
model.setPrefWidth(600);
model.setPromptText("e.g. MyFreeCams:ModelName or an URL like https://chaturbate.com/modelname/");
model.onActionHandler(this::addModel);

View File

@ -9,17 +9,24 @@
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<appender name="FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>ctbrec.log</file>
<append>false</append>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<append>true</append>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<encoder>
<pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n
</pattern>
<pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>ctbrec.%i.log</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>3</maxIndex>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>5MB</maxFileSize>
</triggeringPolicy>
</appender>
<root level="DEBUG">

View File

@ -8,7 +8,7 @@
<parent>
<groupId>ctbrec</groupId>
<artifactId>master</artifactId>
<version>3.8.0</version>
<version>3.8.1</version>
<relativePath>../master</relativePath>
</parent>

View File

@ -17,6 +17,12 @@ public class Java {
return 11;
case "12":
return 12;
case "13":
return 13;
case "14":
return 14;
case "15":
return 15;
default:
return 0;
}

View File

@ -70,7 +70,7 @@ public class Settings {
public boolean livePreviews = false;
public boolean localRecording = true;
public int minimumResolution = 0;
public int maximumResolution = 8160;
public int maximumResolution = 8640;
public int maximumResolutionPlayer = 0;
public String mediaPlayer = "/usr/bin/mpv";
public String mediaPlayerParams = "";

View File

@ -108,7 +108,7 @@ public class BongaCams extends AbstractSite {
@Override
public HttpClient getHttpClient() {
if(httpClient == null) {
if (httpClient == null) {
httpClient = new BongaCamsHttpClient();
}
return httpClient;
@ -116,7 +116,7 @@ public class BongaCams extends AbstractSite {
@Override
public void shutdown() {
if(httpClient != null) {
if (httpClient != null) {
httpClient.shutdown();
}
}

View File

@ -72,7 +72,7 @@ public class Cam4 extends AbstractSite {
@Override
public HttpClient getHttpClient() {
if(httpClient == null) {
if (httpClient == null) {
httpClient = new Cam4HttpClient();
}
return httpClient;
@ -80,7 +80,9 @@ public class Cam4 extends AbstractSite {
@Override
public void shutdown() {
getHttpClient().shutdown();
if (httpClient != null) {
httpClient.shutdown();
}
}
@Override

View File

@ -90,7 +90,7 @@ public class Camsoda extends AbstractSite {
@Override
public HttpClient getHttpClient() {
if(httpClient == null) {
if (httpClient == null) {
httpClient = new CamsodaHttpClient();
}
return httpClient;
@ -103,7 +103,7 @@ public class Camsoda extends AbstractSite {
@Override
public void shutdown() {
if(httpClient != null) {
if (httpClient != null) {
httpClient.shutdown();
}
}

View File

@ -89,7 +89,7 @@ public class Chaturbate extends AbstractSite {
@Override
public HttpClient getHttpClient() {
if(httpClient == null) {
if (httpClient == null) {
httpClient = new ChaturbateHttpClient();
}
return httpClient;
@ -97,7 +97,9 @@ public class Chaturbate extends AbstractSite {
@Override
public void shutdown() {
getHttpClient().shutdown();
if (httpClient != null) {
httpClient.shutdown();
}
}
@Override

View File

@ -73,7 +73,7 @@ public class Fc2Live extends AbstractSite {
@Override
public HttpClient getHttpClient() {
if(httpClient == null) {
if (httpClient == null) {
httpClient = new Fc2HttpClient();
}
return httpClient;
@ -85,7 +85,7 @@ public class Fc2Live extends AbstractSite {
@Override
public void shutdown() {
if(httpClient != null) {
if (httpClient != null) {
httpClient.shutdown();
}
}

View File

@ -89,7 +89,7 @@ public class MyFreeCams extends AbstractSite {
@Override
public MyFreeCamsHttpClient getHttpClient() {
if(httpClient == null) {
if (httpClient == null) {
httpClient = new MyFreeCamsHttpClient();
}
return httpClient;
@ -97,7 +97,9 @@ public class MyFreeCams extends AbstractSite {
@Override
public void shutdown() {
httpClient.shutdown();
if (httpClient != null) {
httpClient.shutdown();
}
}
@Override

View File

@ -455,17 +455,19 @@ public class MyFreeCamsClient {
}
private Message parseMessage(StringBuilder msgBuffer) throws UnsupportedEncodingException {
if (msgBuffer.length() < 4) {
int packetLengthBytes = 6;
if (msgBuffer.length() < packetLengthBytes) {
// packet size not transmitted completely
return null;
} else {
try {
int packetLength = Integer.parseInt(msgBuffer.substring(0, 4));
if (packetLength > msgBuffer.length() - 4) {
int packetLength = Integer.parseInt(msgBuffer.substring(0, packetLengthBytes));
if (packetLength > msgBuffer.length() - packetLengthBytes) {
// packet not complete
return null;
} else {
msgBuffer.delete(0, 4);
LOG.trace("<-- {}", msgBuffer);
msgBuffer.delete(0, packetLengthBytes);
StringBuilder rawMessage = new StringBuilder(msgBuffer.substring(0, packetLength));
int type = parseNextInt(rawMessage);
int sender = parseNextInt(rawMessage);

View File

@ -87,7 +87,7 @@ public class Stripchat extends AbstractSite {
@Override
public HttpClient getHttpClient() {
if(httpClient == null) {
if (httpClient == null) {
httpClient = new StripchatHttpClient();
}
return httpClient;
@ -100,7 +100,7 @@ public class Stripchat extends AbstractSite {
@Override
public void shutdown() {
if(httpClient != null) {
if (httpClient != null) {
httpClient.shutdown();
}
}

View File

@ -6,7 +6,7 @@
<groupId>ctbrec</groupId>
<artifactId>master</artifactId>
<packaging>pom</packaging>
<version>3.8.0</version>
<version>3.8.1</version>
<modules>
<module>../common</module>

View File

@ -8,7 +8,7 @@
<parent>
<groupId>ctbrec</groupId>
<artifactId>master</artifactId>
<version>3.8.0</version>
<version>3.8.1</version>
<relativePath>../master</relativePath>
</parent>

View File

@ -9,17 +9,24 @@
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<appender name="FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>server.log</file>
<append>false</append>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<append>true</append>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<encoder>
<pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n
</pattern>
<pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>server.%i.log</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>3</maxIndex>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>5MB</maxFileSize>
</triggeringPolicy>
</appender>
<root level="DEBUG">