Add timeout window when not record
This commit is contained in:
parent
c515ce7b2a
commit
3c6c495a5b
|
@ -8,7 +8,7 @@
|
|||
<parent>
|
||||
<groupId>ctbrec</groupId>
|
||||
<artifactId>master</artifactId>
|
||||
<version>4.5.2</version>
|
||||
<version>4.5.3</version>
|
||||
<relativePath>../master</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -0,0 +1,258 @@
|
|||
package ctbrec.ui;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.IntegerBinding;
|
||||
import javafx.beans.property.ReadOnlyIntegerProperty;
|
||||
import javafx.beans.property.ReadOnlyIntegerWrapper;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.IndexRange;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
public class TimeTextFieldTest extends Application {
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) {
|
||||
VBox root = new VBox(5);
|
||||
root.setPadding(new Insets(5));
|
||||
Label hrLabel = new Label();
|
||||
Label minLabel = new Label();
|
||||
Label secLabel = new Label();
|
||||
TimeTextField timeTextField = new TimeTextField();
|
||||
hrLabel.textProperty().bind(Bindings.format("Hours: %d", timeTextField.hoursProperty()));
|
||||
minLabel.textProperty().bind(Bindings.format("Minutes: %d", timeTextField.minutesProperty()));
|
||||
secLabel.textProperty().bind(Bindings.format("Seconds: %d", timeTextField.secondsProperty()));
|
||||
|
||||
root.getChildren().addAll(timeTextField, hrLabel, minLabel, secLabel);
|
||||
Scene scene = new Scene(root);
|
||||
primaryStage.setScene(scene);
|
||||
primaryStage.show();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
|
||||
}
|
||||
|
||||
public static class TimeTextField extends TextField {
|
||||
|
||||
enum Unit {
|
||||
HOURS, MINUTES, SECONDS
|
||||
};
|
||||
|
||||
private final Pattern timePattern;
|
||||
private final ReadOnlyIntegerWrapper hours;
|
||||
private final ReadOnlyIntegerWrapper minutes;
|
||||
private final ReadOnlyIntegerWrapper seconds;
|
||||
|
||||
public TimeTextField() {
|
||||
this("00:00:00");
|
||||
}
|
||||
|
||||
public TimeTextField(String time) {
|
||||
super(time);
|
||||
timePattern = Pattern.compile("\\d\\d:\\d\\d:\\d\\d");
|
||||
if (!validate(time)) {
|
||||
throw new IllegalArgumentException("Invalid time: " + time);
|
||||
}
|
||||
hours = new ReadOnlyIntegerWrapper(this, "hours");
|
||||
minutes = new ReadOnlyIntegerWrapper(this, "minutes");
|
||||
seconds = new ReadOnlyIntegerWrapper(this, "seconds");
|
||||
hours.bind(new TimeTextField.TimeUnitBinding(Unit.HOURS));
|
||||
minutes.bind(new TimeTextField.TimeUnitBinding(Unit.MINUTES));
|
||||
seconds.bind(new TimeTextField.TimeUnitBinding(Unit.SECONDS));
|
||||
}
|
||||
|
||||
public ReadOnlyIntegerProperty hoursProperty() {
|
||||
return hours.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public int getHours() {
|
||||
return hours.get();
|
||||
}
|
||||
|
||||
public ReadOnlyIntegerProperty minutesProperty() {
|
||||
return minutes.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public int getMinutes() {
|
||||
return minutes.get();
|
||||
}
|
||||
|
||||
public ReadOnlyIntegerProperty secondsProperty() {
|
||||
return seconds.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public int getSeconds() {
|
||||
return seconds.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendText(String text) {
|
||||
// Ignore this. Our text is always 8 characters long, we cannot append anything
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteNextChar() {
|
||||
|
||||
boolean success = false;
|
||||
|
||||
// If there's a selection, delete it:
|
||||
final IndexRange selection = getSelection();
|
||||
if (selection.getLength() > 0) {
|
||||
int selectionEnd = selection.getEnd();
|
||||
this.deleteText(selection);
|
||||
this.positionCaret(selectionEnd);
|
||||
success = true;
|
||||
} else {
|
||||
// If the caret preceeds a digit, replace that digit with a zero and move the caret forward. Else just move the caret forward.
|
||||
int caret = this.getCaretPosition();
|
||||
if (caret % 3 != 2) { // not preceeding a colon
|
||||
String currentText = this.getText();
|
||||
setText(currentText.substring(0, caret) + "0" + currentText.substring(caret + 1));
|
||||
success = true;
|
||||
}
|
||||
this.positionCaret(Math.min(caret + 1, this.getText().length()));
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deletePreviousChar() {
|
||||
|
||||
boolean success = false;
|
||||
|
||||
// If there's a selection, delete it:
|
||||
final IndexRange selection = getSelection();
|
||||
if (selection.getLength() > 0) {
|
||||
int selectionStart = selection.getStart();
|
||||
this.deleteText(selection);
|
||||
this.positionCaret(selectionStart);
|
||||
success = true;
|
||||
} else {
|
||||
// If the caret is after a digit, replace that digit with a zero and move the caret backward. Else just move the caret back.
|
||||
int caret = this.getCaretPosition();
|
||||
if (caret % 3 != 0) { // not following a colon
|
||||
String currentText = this.getText();
|
||||
setText(currentText.substring(0, caret - 1) + "0" + currentText.substring(caret));
|
||||
success = true;
|
||||
}
|
||||
this.positionCaret(Math.max(caret - 1, 0));
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteText(IndexRange range) {
|
||||
this.deleteText(range.getStart(), range.getEnd());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteText(int begin, int end) {
|
||||
// Replace all digits in the given range with zero:
|
||||
StringBuilder builder = new StringBuilder(this.getText());
|
||||
for (int c = begin; c < end; c++) {
|
||||
if (c % 3 != 2) { // Not at a colon:
|
||||
builder.replace(c, c + 1, "0");
|
||||
}
|
||||
}
|
||||
this.setText(builder.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertText(int index, String text) {
|
||||
// Handle an insert by replacing the range from index to index+text.length() with text, if that results in a valid string:
|
||||
StringBuilder builder = new StringBuilder(this.getText());
|
||||
builder.replace(index, index + text.length(), text);
|
||||
final String testText = builder.toString();
|
||||
if (validate(testText)) {
|
||||
this.setText(testText);
|
||||
}
|
||||
this.positionCaret(index + text.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceSelection(String replacement) {
|
||||
final IndexRange selection = this.getSelection();
|
||||
if (selection.getLength() == 0) {
|
||||
this.insertText(selection.getStart(), replacement);
|
||||
} else {
|
||||
this.replaceText(selection.getStart(), selection.getEnd(), replacement);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceText(IndexRange range, String text) {
|
||||
this.replaceText(range.getStart(), range.getEnd(), text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceText(int begin, int end, String text) {
|
||||
if (begin == end) {
|
||||
this.insertText(begin, text);
|
||||
} else {
|
||||
// only handle this if text.length() is equal to the number of characters being replaced, and if the replacement results in a valid string:
|
||||
if (text.length() == end - begin) {
|
||||
StringBuilder builder = new StringBuilder(this.getText());
|
||||
builder.replace(begin, end, text);
|
||||
String testText = builder.toString();
|
||||
if (validate(testText)) {
|
||||
this.setText(testText);
|
||||
}
|
||||
this.positionCaret(end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean validate(String time) {
|
||||
if (!timePattern.matcher(time).matches()) {
|
||||
return false;
|
||||
}
|
||||
String[] tokens = time.split(":");
|
||||
assert tokens.length == 3;
|
||||
try {
|
||||
int hours = Integer.parseInt(tokens[0]);
|
||||
int mins = Integer.parseInt(tokens[1]);
|
||||
int secs = Integer.parseInt(tokens[2]);
|
||||
if (hours < 0 || hours > 23) {
|
||||
return false;
|
||||
}
|
||||
if (mins < 0 || mins > 59) {
|
||||
return false;
|
||||
}
|
||||
if (secs < 0 || secs > 59) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (NumberFormatException nfe) {
|
||||
// regex matching should assure we never reach this catch block
|
||||
assert false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private final class TimeUnitBinding extends IntegerBinding {
|
||||
|
||||
final Unit unit;
|
||||
|
||||
TimeUnitBinding(Unit unit) {
|
||||
this.bind(textProperty());
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int computeValue() {
|
||||
// Crazy enum magic
|
||||
String token = getText().split(":")[unit.ordinal()];
|
||||
return Integer.parseInt(token);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package ctbrec.ui.controls;
|
||||
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Optional;
|
||||
|
||||
import javafx.scene.control.Spinner;
|
||||
import javafx.scene.control.SpinnerValueFactory;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.input.ScrollEvent;
|
||||
|
||||
public class TimePicker extends Spinner<LocalTime> {
|
||||
|
||||
public TimePicker() {
|
||||
this(LocalTime.now().truncatedTo(ChronoUnit.MINUTES));
|
||||
}
|
||||
|
||||
public TimePicker(LocalTime value) {
|
||||
setValueFactory(new TimePickerValueFactory());
|
||||
getValueFactory().setValue(value);
|
||||
setEditable(true);
|
||||
getEditor().setOnKeyReleased(this::updateValueFromInput);
|
||||
getEditor().focusedProperty().addListener((obs, oldV, focused) -> {
|
||||
if (Boolean.FALSE.equals(focused)) {
|
||||
getEditor().setText(getValue().toString());
|
||||
}
|
||||
});
|
||||
setOnScroll(this::onScroll);
|
||||
}
|
||||
|
||||
private void onScroll(ScrollEvent evt) {
|
||||
int d = (int) evt.getDeltaY();
|
||||
int units = (int) (Math.abs(d) / evt.getMultiplierY());
|
||||
if (d > 0) {
|
||||
getValueFactory().increment(units);
|
||||
} else {
|
||||
getValueFactory().decrement(units);
|
||||
}
|
||||
evt.consume();
|
||||
}
|
||||
|
||||
private void updateValueFromInput(KeyEvent evt) {
|
||||
String input = getEditor().getText();
|
||||
try {
|
||||
LocalTime newValue = LocalTime.parse(input);
|
||||
getValueFactory().setValue(newValue);
|
||||
} catch (DateTimeParseException e) {
|
||||
// input is invalid, we do nothing
|
||||
}
|
||||
}
|
||||
|
||||
private class TimePickerValueFactory extends SpinnerValueFactory<LocalTime> {
|
||||
@Override
|
||||
public void decrement(int steps) {
|
||||
LocalTime time = Optional.ofNullable(getValue()).orElse(LocalTime.now().truncatedTo(ChronoUnit.MINUTES));
|
||||
setValue(time.minusMinutes(steps));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void increment(int steps) {
|
||||
LocalTime time = Optional.ofNullable(getValue()).orElse(LocalTime.now().truncatedTo(ChronoUnit.MINUTES));
|
||||
setValue(time.plusMinutes(steps));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package ctbrec.ui.settings;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalTime;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
@ -13,9 +14,11 @@ import ctbrec.Settings;
|
|||
import ctbrec.StringUtil;
|
||||
import ctbrec.ui.controls.DirectorySelectionBox;
|
||||
import ctbrec.ui.controls.ProgramSelectionBox;
|
||||
import ctbrec.ui.controls.TimePicker;
|
||||
import ctbrec.ui.controls.range.DiscreteRange;
|
||||
import ctbrec.ui.controls.range.RangeSlider;
|
||||
import ctbrec.ui.settings.api.ExclusiveSelectionProperty;
|
||||
import ctbrec.ui.settings.api.LocalTimeProperty;
|
||||
import ctbrec.ui.settings.api.Preferences;
|
||||
import ctbrec.ui.settings.api.PreferencesStorage;
|
||||
import ctbrec.ui.settings.api.Setting;
|
||||
|
@ -82,6 +85,8 @@ public class CtbrecPreferencesStorage implements PreferencesStorage {
|
|||
return createDirectorySelector(setting);
|
||||
} else if (prop instanceof SimpleFileProperty) {
|
||||
return createFileSelector(setting);
|
||||
} else if (prop instanceof LocalTimeProperty) {
|
||||
return createTimeSelector(setting);
|
||||
} else if (prop instanceof IntegerProperty) {
|
||||
return createIntegerProperty(setting);
|
||||
} else if (prop instanceof LongProperty) {
|
||||
|
@ -212,6 +217,23 @@ public class CtbrecPreferencesStorage implements PreferencesStorage {
|
|||
return directorySelector;
|
||||
}
|
||||
|
||||
private Node createTimeSelector(Setting setting) {
|
||||
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 (setting.doesNeedRestart()) {
|
||||
runRestartRequiredCallback();
|
||||
}
|
||||
config.save();
|
||||
}
|
||||
}));
|
||||
return timePicker;
|
||||
}
|
||||
|
||||
private Node createStringProperty(Setting setting) {
|
||||
var ctrl = new TextField();
|
||||
ctrl.textProperty().addListener((obs, oldV, newV) -> saveValue(() -> {
|
||||
|
|
|
@ -31,6 +31,7 @@ import ctbrec.ui.settings.api.Category;
|
|||
import ctbrec.ui.settings.api.ExclusiveSelectionProperty;
|
||||
import ctbrec.ui.settings.api.GigabytesConverter;
|
||||
import ctbrec.ui.settings.api.Group;
|
||||
import ctbrec.ui.settings.api.LocalTimeProperty;
|
||||
import ctbrec.ui.settings.api.Preferences;
|
||||
import ctbrec.ui.settings.api.Setting;
|
||||
import ctbrec.ui.settings.api.SimpleDirectoryProperty;
|
||||
|
@ -139,6 +140,8 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
private SimpleBooleanProperty minimizeToTray;
|
||||
private SimpleBooleanProperty showGridLinesInTables;
|
||||
private SimpleIntegerProperty defaultPriority;
|
||||
private LocalTimeProperty timeoutRecordingStartingAt;
|
||||
private LocalTimeProperty timeoutRecordingEndingAt;
|
||||
|
||||
public SettingsTab(List<Site> sites, Recorder recorder) {
|
||||
this.sites = sites;
|
||||
|
@ -205,6 +208,8 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
minimizeToTray = new SimpleBooleanProperty(null, "minimizeToTray", settings.minimizeToTray);
|
||||
showGridLinesInTables = new SimpleBooleanProperty(null, "showGridLinesInTables", settings.showGridLinesInTables);
|
||||
defaultPriority = new SimpleIntegerProperty(null, "defaultPriority", settings.defaultPriority);
|
||||
timeoutRecordingStartingAt = new LocalTimeProperty(null, "timeoutRecordingStartingAt", settings.timeoutRecordingStartingAt);
|
||||
timeoutRecordingEndingAt = new LocalTimeProperty(null, "timeoutRecordingEndingAt", settings.timeoutRecordingEndingAt);
|
||||
}
|
||||
|
||||
private void createGui() {
|
||||
|
@ -243,7 +248,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
Setting.of("Show grid lines in tables", showGridLinesInTables, "Show grid lines in tables").needsRestart(),
|
||||
Setting.of("Fast scroll speed", fastScrollSpeed, "Makes the thumbnail overviews scroll faster with the mouse wheel").needsRestart())),
|
||||
Category.of("Recorder",
|
||||
Group.of("Settings", Setting.of("Recordings Directory", recordingsDir), Setting.of("Directory Structure", directoryStructure),
|
||||
Group.of("Recorder", Setting.of("Recordings Directory", recordingsDir), Setting.of("Directory Structure", directoryStructure),
|
||||
Setting.of("Split recordings after", splitAfter).converter(SplitAfterOption.converter()).onChange(this::splitValuesChanged),
|
||||
Setting.of("Split recordings bigger than", splitBiggerThan).converter(SplitBiggerThanOption.converter())
|
||||
.onChange(this::splitValuesChanged),
|
||||
|
@ -256,6 +261,10 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
Setting.of("File Extension", fileExtension, "File extension to use for recordings"),
|
||||
Setting.of("Check online state every (seconds)", onlineCheckIntervalInSecs, "Check every x seconds, if a model came online"),
|
||||
Setting.of("Skip online check for paused models", onlineCheckSkipsPausedModels, "Skip online check for paused models")),
|
||||
Group.of("Timeout",
|
||||
Setting.of("Don't record from", timeoutRecordingStartingAt),
|
||||
Setting.of("Until", timeoutRecordingEndingAt)
|
||||
),
|
||||
Group.of("Location", Setting.of("Record Location", recordLocal).needsRestart(), Setting.of("Server", server), Setting.of("Port", port),
|
||||
Setting.of("Path", path, "Leave empty, if you didn't change the servletContext in the server config"),
|
||||
Setting.of("Download Filename", downloadFilename, "File name pattern for downloads"), Setting.of("", variablesHelpButton),
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package ctbrec.ui.settings.api;
|
||||
|
||||
import java.time.LocalTime;
|
||||
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
|
||||
public class LocalTimeProperty extends SimpleObjectProperty<LocalTime> {
|
||||
|
||||
public LocalTimeProperty(Object bean, String name, LocalTime initialValue) {
|
||||
super(bean, name, initialValue);
|
||||
}
|
||||
|
||||
}
|
|
@ -8,7 +8,7 @@
|
|||
<parent>
|
||||
<groupId>ctbrec</groupId>
|
||||
<artifactId>master</artifactId>
|
||||
<version>4.5.2</version>
|
||||
<version>4.5.3</version>
|
||||
<relativePath>../master</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import java.nio.file.StandardCopyOption;
|
|||
import java.text.SimpleDateFormat;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Date;
|
||||
|
@ -30,6 +31,7 @@ import com.squareup.moshi.Moshi;
|
|||
|
||||
import ctbrec.Settings.SplitStrategy;
|
||||
import ctbrec.io.FileJsonAdapter;
|
||||
import ctbrec.io.LocalTimeJsonAdapter;
|
||||
import ctbrec.io.ModelJsonAdapter;
|
||||
import ctbrec.io.PostProcessorJsonAdapter;
|
||||
import ctbrec.io.UuidJSonAdapter;
|
||||
|
@ -78,6 +80,7 @@ public class Config {
|
|||
.add(PostProcessor.class, new PostProcessorJsonAdapter())
|
||||
.add(File.class, new FileJsonAdapter())
|
||||
.add(UUID.class, new UuidJSonAdapter())
|
||||
.add(LocalTime.class, new LocalTimeJsonAdapter())
|
||||
.build();
|
||||
JsonAdapter<Settings> adapter = moshi.adapter(Settings.class).lenient();
|
||||
File configFile = new File(configDir, filename);
|
||||
|
@ -239,6 +242,7 @@ public class Config {
|
|||
.add(PostProcessor.class, new PostProcessorJsonAdapter())
|
||||
.add(File.class, new FileJsonAdapter())
|
||||
.add(UUID.class, new UuidJSonAdapter())
|
||||
.add(LocalTime.class, new LocalTimeJsonAdapter())
|
||||
.build();
|
||||
JsonAdapter<Settings> adapter = moshi.adapter(Settings.class).indent(" ");
|
||||
String json = adapter.toJson(settings);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package ctbrec;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.LocalTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
@ -177,6 +178,8 @@ public class Settings {
|
|||
public String stripchatPassword = "";
|
||||
public boolean stripchatUseXhamster = false;
|
||||
public List<String> tabOrder = new ArrayList<>();
|
||||
public LocalTime timeoutRecordingStartingAt = LocalTime.of(0, 0);
|
||||
public LocalTime timeoutRecordingEndingAt = LocalTime.of(0, 0);
|
||||
public boolean totalModelCountInTitle = false;
|
||||
public boolean transportLayerSecurity = true;
|
||||
public int thumbWidth = 180;
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package ctbrec.io;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalTime;
|
||||
|
||||
import com.squareup.moshi.JsonAdapter;
|
||||
import com.squareup.moshi.JsonReader;
|
||||
import com.squareup.moshi.JsonWriter;
|
||||
|
||||
public class LocalTimeJsonAdapter extends JsonAdapter<LocalTime> {
|
||||
@Override
|
||||
public LocalTime fromJson(JsonReader reader) throws IOException {
|
||||
String timeString = reader.nextString();
|
||||
return LocalTime.parse(timeString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toJson(JsonWriter writer, LocalTime time) throws IOException {
|
||||
writer.value(time.toString());
|
||||
}
|
||||
}
|
|
@ -101,7 +101,7 @@ public class NextGenLocalRecorder implements Recorder {
|
|||
recording = true;
|
||||
registerEventBusListener();
|
||||
|
||||
preconditions = new RecordingPreconditions(this);
|
||||
preconditions = new RecordingPreconditions(this, config);
|
||||
|
||||
LOG.debug("Recorder initialized");
|
||||
LOG.info("Models to record: {}", models);
|
||||
|
@ -286,7 +286,7 @@ public class NextGenLocalRecorder implements Recorder {
|
|||
private CompletableFuture<Void> startRecordingProcess(Model model) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
preconditions.check(model, config);
|
||||
preconditions.check(model);
|
||||
LOG.info("Starting recording for model {}", model.getName());
|
||||
Download download = createDownload(model);
|
||||
recorderLock.lock();
|
||||
|
|
|
@ -5,6 +5,7 @@ import java.io.IOException;
|
|||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalTime;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
@ -28,14 +29,15 @@ public class RecordingPreconditions {
|
|||
private long lastPreconditionMessage = 0;
|
||||
|
||||
|
||||
RecordingPreconditions(NextGenLocalRecorder recorder) {
|
||||
RecordingPreconditions(NextGenLocalRecorder recorder, Config config) {
|
||||
this.recorder = recorder;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
void check(Model model, Config config) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
|
||||
this.config = config;
|
||||
void check(Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
|
||||
LOG.debug("Checking preconditions for model {}", model);
|
||||
ensureRecorderIsActive();
|
||||
ensureNotInTimeoutPeriod();
|
||||
ensureModelIsNotSuspended(model);
|
||||
ensureModelIsNotMarkedForLaterRecording(model);
|
||||
ensureRecordUntilIsInFuture(model);
|
||||
|
@ -47,6 +49,21 @@ public class RecordingPreconditions {
|
|||
ensureDownloadSlotAvailable(model);
|
||||
}
|
||||
|
||||
private void ensureNotInTimeoutPeriod() {
|
||||
LocalTime start = config.getSettings().timeoutRecordingStartingAt;
|
||||
LocalTime end = config.getSettings().timeoutRecordingEndingAt;
|
||||
if (start.equals(end)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LocalTime now = LocalTime.now();
|
||||
if (start.isBefore(end) && now.isAfter(start) && now.isBefore(end)) {
|
||||
throw new PreconditionNotMetException("Current time is in recording timeout " + start + " - " + end);
|
||||
} else if(start.isAfter(end) && !(now.isAfter(end) && now.isBefore(start))) { // NOSONAR
|
||||
throw new PreconditionNotMetException("Current time is in recording timeout " + start + " - " + end);
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureModelIsOnline(Model model) {
|
||||
try {
|
||||
if (!model.isOnline(IGNORE_CACHE)) {
|
||||
|
|
|
@ -8,6 +8,8 @@ import java.io.IOException;
|
|||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
@ -45,8 +47,8 @@ class RecordingPreconditionsTest {
|
|||
NextGenLocalRecorder recorder = mock(NextGenLocalRecorder.class);
|
||||
Model model = mock(Model.class);
|
||||
|
||||
RecordingPreconditions preconditions = new RecordingPreconditions(recorder);
|
||||
PreconditionNotMetException ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(model, config));
|
||||
RecordingPreconditions preconditions = new RecordingPreconditions(recorder, config);
|
||||
PreconditionNotMetException ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(model));
|
||||
assertEquals("Recorder is not in recording mode", ex.getMessage());
|
||||
}
|
||||
|
||||
|
@ -59,8 +61,8 @@ class RecordingPreconditionsTest {
|
|||
when(model.isSuspended()).thenReturn(true);
|
||||
when(model.toString()).thenReturn("Mockita Boobilicious");
|
||||
|
||||
RecordingPreconditions preconditions = new RecordingPreconditions(recorder);
|
||||
PreconditionNotMetException ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(model, config));
|
||||
RecordingPreconditions preconditions = new RecordingPreconditions(recorder, config);
|
||||
PreconditionNotMetException ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(model));
|
||||
assertEquals("Recording for model Mockita Boobilicious is suspended", ex.getMessage());
|
||||
}
|
||||
|
||||
|
@ -73,8 +75,8 @@ class RecordingPreconditionsTest {
|
|||
when(model.isMarkedForLaterRecording()).thenReturn(true);
|
||||
when(model.toString()).thenReturn("Mockita Boobilicious");
|
||||
|
||||
RecordingPreconditions preconditions = new RecordingPreconditions(recorder);
|
||||
PreconditionNotMetException ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(model, config));
|
||||
RecordingPreconditions preconditions = new RecordingPreconditions(recorder, config);
|
||||
PreconditionNotMetException ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(model));
|
||||
assertEquals("Model Mockita Boobilicious is marked for later recording", ex.getMessage());
|
||||
}
|
||||
|
||||
|
@ -87,8 +89,8 @@ class RecordingPreconditionsTest {
|
|||
when(model.getRecordUntil()).thenReturn(Instant.now().minus(1, HOURS));
|
||||
when(model.toString()).thenReturn("Mockita Boobilicious");
|
||||
|
||||
RecordingPreconditions preconditions = new RecordingPreconditions(recorder);
|
||||
PreconditionNotMetException ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(model, config));
|
||||
RecordingPreconditions preconditions = new RecordingPreconditions(recorder, config);
|
||||
PreconditionNotMetException ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(model));
|
||||
assertTrue(ex.getMessage().contains("Recording expired at "));
|
||||
}
|
||||
|
||||
|
@ -104,8 +106,8 @@ class RecordingPreconditionsTest {
|
|||
when(model.getRecordUntil()).thenReturn(Instant.MAX);
|
||||
when(model.toString()).thenReturn("Mockita Boobilicious");
|
||||
|
||||
RecordingPreconditions preconditions = new RecordingPreconditions(recorder);
|
||||
PreconditionNotMetException ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(model, config));
|
||||
RecordingPreconditions preconditions = new RecordingPreconditions(recorder, config);
|
||||
PreconditionNotMetException ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(model));
|
||||
assertEquals("A recording for model Mockita Boobilicious is already running", ex.getMessage());
|
||||
}
|
||||
|
||||
|
@ -121,8 +123,8 @@ class RecordingPreconditionsTest {
|
|||
when(model.toString()).thenReturn("Mockita Boobilicious");
|
||||
when(model.isOnline(true)).thenReturn(true);
|
||||
|
||||
RecordingPreconditions preconditions = new RecordingPreconditions(recorder);
|
||||
PreconditionNotMetException ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(model, config));
|
||||
RecordingPreconditions preconditions = new RecordingPreconditions(recorder, config);
|
||||
PreconditionNotMetException ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(model));
|
||||
assertEquals("Model Mockita Boobilicious has been removed. Restarting of recording cancelled.", ex.getMessage());
|
||||
|
||||
modelsToRecord.add(model);
|
||||
|
@ -130,7 +132,7 @@ class RecordingPreconditionsTest {
|
|||
when(recorder.isRecording()).thenReturn(true);
|
||||
when(recorder.getModels()).thenReturn(modelsToRecord);
|
||||
when(recorder.enoughSpaceForRecording()).thenReturn(true);
|
||||
assertDoesNotThrow(() -> preconditions.check(model, config));
|
||||
assertDoesNotThrow(() -> preconditions.check(model));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -147,8 +149,8 @@ class RecordingPreconditionsTest {
|
|||
when(recorder.getModels()).thenReturn(modelsToRecord);
|
||||
when(recorder.enoughSpaceForRecording()).thenReturn(false);
|
||||
|
||||
RecordingPreconditions preconditions = new RecordingPreconditions(recorder);
|
||||
PreconditionNotMetException ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(model, config));
|
||||
RecordingPreconditions preconditions = new RecordingPreconditions(recorder, config);
|
||||
PreconditionNotMetException ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(model));
|
||||
assertEquals("Not enough disk space for recording", ex.getMessage());
|
||||
}
|
||||
|
||||
|
@ -185,8 +187,8 @@ class RecordingPreconditionsTest {
|
|||
when(recorder.getRecordingProcesses()).thenReturn(recordingProcesses);
|
||||
|
||||
|
||||
RecordingPreconditions preconditions = new RecordingPreconditions(recorder);
|
||||
PreconditionNotMetException ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(mockita, config));
|
||||
RecordingPreconditions preconditions = new RecordingPreconditions(recorder, config);
|
||||
PreconditionNotMetException ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(mockita));
|
||||
assertEquals("The Other One from the same group is already recorded", ex.getMessage());
|
||||
}
|
||||
|
||||
|
@ -206,8 +208,8 @@ class RecordingPreconditionsTest {
|
|||
when(recorder.getModels()).thenReturn(modelsToRecord);
|
||||
when(recorder.enoughSpaceForRecording()).thenReturn(true);
|
||||
|
||||
RecordingPreconditions preconditions = new RecordingPreconditions(recorder);
|
||||
PreconditionNotMetException ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(mockita, config));
|
||||
RecordingPreconditions preconditions = new RecordingPreconditions(recorder, config);
|
||||
PreconditionNotMetException ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(mockita));
|
||||
assertEquals("Mockita Boobilicious's room is not public", ex.getMessage());
|
||||
}
|
||||
|
||||
|
@ -226,15 +228,15 @@ class RecordingPreconditionsTest {
|
|||
when(recorder.getModels()).thenReturn(modelsToRecord);
|
||||
when(recorder.enoughSpaceForRecording()).thenReturn(true);
|
||||
|
||||
RecordingPreconditions preconditions = new RecordingPreconditions(recorder);
|
||||
PreconditionNotMetException ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(mockita, config));
|
||||
RecordingPreconditions preconditions = new RecordingPreconditions(recorder, config);
|
||||
PreconditionNotMetException ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(mockita));
|
||||
assertEquals("Mockita Boobilicious's room is not public", ex.getMessage());
|
||||
|
||||
reset(mockita);
|
||||
when(mockita.isOnline(true)).thenThrow(new InterruptedException());
|
||||
when(mockita.getRecordUntil()).thenReturn(Instant.MAX);
|
||||
when(mockita.getName()).thenReturn("Mockita Boobilicious");
|
||||
ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(mockita, config));
|
||||
ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(mockita));
|
||||
assertEquals("Mockita Boobilicious's room is not public", ex.getMessage());
|
||||
}
|
||||
|
||||
|
@ -264,20 +266,20 @@ class RecordingPreconditionsTest {
|
|||
recordingProcesses.put(theOtherOne, new Recording());
|
||||
when(recorder.getRecordingProcesses()).thenReturn(recordingProcesses);
|
||||
|
||||
RecordingPreconditions preconditions = new RecordingPreconditions(recorder);
|
||||
PreconditionNotMetException ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(mockita, config));
|
||||
RecordingPreconditions preconditions = new RecordingPreconditions(recorder, config);
|
||||
PreconditionNotMetException ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(mockita));
|
||||
assertEquals("Other models have higher prio, not starting recording for Mockita Boobilicious", ex.getMessage());
|
||||
|
||||
settings.concurrentRecordings = -1;
|
||||
ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(mockita, config));
|
||||
ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(mockita));
|
||||
assertEquals("Other models have higher prio, not starting recording for Mockita Boobilicious", ex.getMessage());
|
||||
|
||||
settings.concurrentRecordings = 0;
|
||||
assertDoesNotThrow(() -> preconditions.check(mockita, config));
|
||||
assertDoesNotThrow(() -> preconditions.check(mockita));
|
||||
|
||||
settings.concurrentRecordings = 1;
|
||||
recordingProcesses.clear();
|
||||
assertDoesNotThrow(() -> preconditions.check(mockita, config));
|
||||
assertDoesNotThrow(() -> preconditions.check(mockita));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -316,11 +318,65 @@ class RecordingPreconditionsTest {
|
|||
when(lowestPrio.getPriority()).thenReturn(1);
|
||||
recordingProcesses.put(theOtherOne, mockRecordingProcess(lowestPrio));
|
||||
|
||||
RecordingPreconditions preconditions = new RecordingPreconditions(recorder);
|
||||
assertDoesNotThrow(() -> preconditions.check(mockita, config));
|
||||
RecordingPreconditions preconditions = new RecordingPreconditions(recorder, config);
|
||||
assertDoesNotThrow(() -> preconditions.check(mockita));
|
||||
verify(recorder).stopRecordingProcess(lowestPrio);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNotInTimeoutPeriod() throws InvalidKeyException, NoSuchAlgorithmException, IOException, ExecutionException, InterruptedException {
|
||||
Model mockita = mock(Model.class);
|
||||
when(mockita.isOnline(true)).thenReturn(true);
|
||||
when(mockita.getRecordUntil()).thenReturn(Instant.MAX);
|
||||
when(mockita.getName()).thenReturn("Mockita Boobilicious");
|
||||
when(mockita.getPriority()).thenReturn(100);
|
||||
NextGenLocalRecorder recorder = mock(NextGenLocalRecorder.class);
|
||||
List<Model> modelsToRecord = new LinkedList<>();
|
||||
settings.models = modelsToRecord;
|
||||
settings.timeoutRecordingStartingAt = LocalTime.now().minusHours(1).truncatedTo(ChronoUnit.MINUTES);
|
||||
settings.timeoutRecordingEndingAt = LocalTime.now().plusHours(1).truncatedTo(ChronoUnit.MINUTES);
|
||||
modelsToRecord.add(mockita);
|
||||
when(recorder.isRecording()).thenReturn(true);
|
||||
when(recorder.getModels()).thenReturn(modelsToRecord);
|
||||
when(recorder.enoughSpaceForRecording()).thenReturn(true);
|
||||
Map<Model, Recording> recordingProcesses = new HashMap<>();
|
||||
when(recorder.getRecordingProcesses()).thenReturn(recordingProcesses);
|
||||
|
||||
RecordingPreconditions preconditions = new RecordingPreconditions(recorder, config);
|
||||
PreconditionNotMetException ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(mockita));
|
||||
assertTrue(ex.getMessage().startsWith("Current time is in recording timeout"));
|
||||
|
||||
settings.timeoutRecordingStartingAt = LocalTime.now().minusHours(1).truncatedTo(ChronoUnit.MINUTES);
|
||||
settings.timeoutRecordingEndingAt = LocalTime.now().minusHours(2).truncatedTo(ChronoUnit.MINUTES);
|
||||
ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(mockita));
|
||||
assertTrue(ex.getMessage().startsWith("Current time is in recording timeout"));
|
||||
|
||||
settings.timeoutRecordingStartingAt = LocalTime.now().minusHours(1).truncatedTo(ChronoUnit.MINUTES);
|
||||
settings.timeoutRecordingEndingAt = LocalTime.now().minusHours(2).truncatedTo(ChronoUnit.MINUTES);
|
||||
ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(mockita));
|
||||
assertTrue(ex.getMessage().startsWith("Current time is in recording timeout"));
|
||||
|
||||
settings.timeoutRecordingStartingAt = LocalTime.now().plusHours(2).truncatedTo(ChronoUnit.MINUTES);
|
||||
settings.timeoutRecordingEndingAt = LocalTime.now().plusHours(1).truncatedTo(ChronoUnit.MINUTES);
|
||||
ex = assertThrows(PreconditionNotMetException.class, () -> preconditions.check(mockita));
|
||||
assertTrue(ex.getMessage().startsWith("Current time is in recording timeout"));
|
||||
|
||||
settings.timeoutRecordingStartingAt = LocalTime.of(0, 0);
|
||||
settings.timeoutRecordingEndingAt = LocalTime.of(0, 0);
|
||||
assertDoesNotThrow(() -> preconditions.check(mockita));
|
||||
|
||||
settings.timeoutRecordingStartingAt = LocalTime.now().plusHours(1).truncatedTo(ChronoUnit.MINUTES);
|
||||
settings.timeoutRecordingEndingAt = LocalTime.now().plusHours(2).truncatedTo(ChronoUnit.MINUTES);
|
||||
assertDoesNotThrow(() -> preconditions.check(mockita));
|
||||
settings.timeoutRecordingStartingAt = LocalTime.now().minusHours(2).truncatedTo(ChronoUnit.MINUTES);
|
||||
settings.timeoutRecordingEndingAt = LocalTime.now().minusHours(1).truncatedTo(ChronoUnit.MINUTES);
|
||||
assertDoesNotThrow(() -> preconditions.check(mockita));
|
||||
|
||||
settings.timeoutRecordingStartingAt = LocalTime.now().plusHours(1).truncatedTo(ChronoUnit.MINUTES);
|
||||
settings.timeoutRecordingEndingAt = LocalTime.now().minusHours(1).truncatedTo(ChronoUnit.MINUTES);
|
||||
assertDoesNotThrow(() -> preconditions.check(mockita));
|
||||
}
|
||||
|
||||
private Recording mockRecordingProcess(Model model) {
|
||||
Download download = mock(Download.class);
|
||||
when(download.getModel()).thenReturn(model);
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<groupId>ctbrec</groupId>
|
||||
<artifactId>master</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>4.5.2</version>
|
||||
<version>4.5.3</version>
|
||||
|
||||
<modules>
|
||||
<module>../common</module>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<parent>
|
||||
<groupId>ctbrec</groupId>
|
||||
<artifactId>master</artifactId>
|
||||
<version>4.5.2</version>
|
||||
<version>4.5.3</version>
|
||||
<relativePath>../master</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
Loading…
Reference in New Issue