Add recording priority for models
Models with high priority will be favored over models with low priority. Recordings for models with low priority might even get stopped to free up a slot for a model with a higher priority
This commit is contained in:
parent
8ae41142d1
commit
4d6e74562c
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -1,3 +1,14 @@
|
||||||
|
3.1.0
|
||||||
|
========================
|
||||||
|
* Added recording priorities for models. If you restrict the number of
|
||||||
|
concurrent downloads, models with high priority will be favored over models
|
||||||
|
with low prio. Running recordings of models with low prio might even get
|
||||||
|
stopped, so that models with higher prio can get recorded.
|
||||||
|
You can adjust the prio on the "Recroding" tab by double-clicking on the
|
||||||
|
value or by using your scroll wheel while holding down CTRL
|
||||||
|
* Added menu entry to open the recording dir of a model
|
||||||
|
|
||||||
|
|
||||||
3.0.4
|
3.0.4
|
||||||
========================
|
========================
|
||||||
* MFC now uses DASH again :) You can switch betwenn DASH and HLS in the settings
|
* MFC now uses DASH again :) You can switch betwenn DASH and HLS in the settings
|
||||||
|
|
|
@ -17,6 +17,7 @@ import ctbrec.recorder.download.StreamSource;
|
||||||
import ctbrec.sites.Site;
|
import ctbrec.sites.Site;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Just a wrapper for Model, which augments it with JavaFX value binding properties, so that UI widgets get updated proeprly
|
* Just a wrapper for Model, which augments it with JavaFX value binding properties, so that UI widgets get updated proeprly
|
||||||
|
@ -25,10 +26,12 @@ public class JavaFxModel implements Model {
|
||||||
private transient BooleanProperty onlineProperty = new SimpleBooleanProperty();
|
private transient BooleanProperty onlineProperty = new SimpleBooleanProperty();
|
||||||
private transient BooleanProperty recordingProperty = new SimpleBooleanProperty();
|
private transient BooleanProperty recordingProperty = new SimpleBooleanProperty();
|
||||||
private transient BooleanProperty pausedProperty = new SimpleBooleanProperty();
|
private transient BooleanProperty pausedProperty = new SimpleBooleanProperty();
|
||||||
|
private transient SimpleIntegerProperty priorityProperty = new SimpleIntegerProperty();
|
||||||
private Model delegate;
|
private Model delegate;
|
||||||
|
|
||||||
public JavaFxModel(Model delegate) {
|
public JavaFxModel(Model delegate) {
|
||||||
this.delegate = delegate;
|
this.delegate = delegate;
|
||||||
|
setPriority(delegate.getPriority());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -103,6 +106,10 @@ public class JavaFxModel implements Model {
|
||||||
return pausedProperty;
|
return pausedProperty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SimpleIntegerProperty getPriorityProperty() {
|
||||||
|
return priorityProperty;
|
||||||
|
}
|
||||||
|
|
||||||
public Model getDelegate() {
|
public Model getDelegate() {
|
||||||
return delegate;
|
return delegate;
|
||||||
}
|
}
|
||||||
|
@ -216,6 +223,17 @@ public class JavaFxModel implements Model {
|
||||||
delegate.setDisplayName(name);
|
delegate.setDisplayName(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPriority(int priority) {
|
||||||
|
delegate.setPriority(priority);
|
||||||
|
priorityProperty.set(priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPriority() {
|
||||||
|
return delegate.getPriority();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(Model o) {
|
public int compareTo(Model o) {
|
||||||
return delegate.compareTo(o);
|
return delegate.compareTo(o);
|
||||||
|
|
|
@ -8,6 +8,7 @@ import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
@ -38,10 +39,12 @@ import ctbrec.ui.controls.SearchBox;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.property.StringPropertyBase;
|
import javafx.beans.property.StringPropertyBase;
|
||||||
|
import javafx.beans.value.ChangeListener;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.concurrent.ScheduledService;
|
import javafx.concurrent.ScheduledService;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
|
import javafx.concurrent.WorkerStateEvent;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
|
@ -53,7 +56,9 @@ import javafx.scene.control.MenuItem;
|
||||||
import javafx.scene.control.ScrollPane;
|
import javafx.scene.control.ScrollPane;
|
||||||
import javafx.scene.control.SelectionMode;
|
import javafx.scene.control.SelectionMode;
|
||||||
import javafx.scene.control.Tab;
|
import javafx.scene.control.Tab;
|
||||||
|
import javafx.scene.control.TableCell;
|
||||||
import javafx.scene.control.TableColumn;
|
import javafx.scene.control.TableColumn;
|
||||||
|
import javafx.scene.control.TableColumn.CellEditEvent;
|
||||||
import javafx.scene.control.TableColumn.SortType;
|
import javafx.scene.control.TableColumn.SortType;
|
||||||
import javafx.scene.control.TableRow;
|
import javafx.scene.control.TableRow;
|
||||||
import javafx.scene.control.TableView;
|
import javafx.scene.control.TableView;
|
||||||
|
@ -61,6 +66,7 @@ import javafx.scene.control.TextField;
|
||||||
import javafx.scene.control.Tooltip;
|
import javafx.scene.control.Tooltip;
|
||||||
import javafx.scene.control.cell.CheckBoxTableCell;
|
import javafx.scene.control.cell.CheckBoxTableCell;
|
||||||
import javafx.scene.control.cell.PropertyValueFactory;
|
import javafx.scene.control.cell.PropertyValueFactory;
|
||||||
|
import javafx.scene.control.cell.TextFieldTableCell;
|
||||||
import javafx.scene.input.Clipboard;
|
import javafx.scene.input.Clipboard;
|
||||||
import javafx.scene.input.ClipboardContent;
|
import javafx.scene.input.ClipboardContent;
|
||||||
import javafx.scene.input.ContextMenuEvent;
|
import javafx.scene.input.ContextMenuEvent;
|
||||||
|
@ -72,7 +78,10 @@ import javafx.scene.layout.BorderPane;
|
||||||
import javafx.scene.layout.FlowPane;
|
import javafx.scene.layout.FlowPane;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.Priority;
|
import javafx.scene.layout.Priority;
|
||||||
|
import javafx.util.Callback;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
|
import javafx.util.StringConverter;
|
||||||
|
import javafx.util.converter.NumberStringConverter;
|
||||||
|
|
||||||
public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(RecordedModelsTab.class);
|
private static final Logger LOG = LoggerFactory.getLogger(RecordedModelsTab.class);
|
||||||
|
@ -81,6 +90,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
private ScheduledService<List<JavaFxModel>> updateService;
|
private ScheduledService<List<JavaFxModel>> updateService;
|
||||||
private Recorder recorder;
|
private Recorder recorder;
|
||||||
private List<Site> sites;
|
private List<Site> sites;
|
||||||
|
private volatile boolean cellEditing = false;
|
||||||
|
|
||||||
FlowPane grid = new FlowPane();
|
FlowPane grid = new FlowPane();
|
||||||
ScrollPane scrollPane = new ScrollPane();
|
ScrollPane scrollPane = new ScrollPane();
|
||||||
|
@ -136,9 +146,11 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
TableColumn<JavaFxModel, String> name = new TableColumn<>("Model");
|
TableColumn<JavaFxModel, String> name = new TableColumn<>("Model");
|
||||||
name.setPrefWidth(200);
|
name.setPrefWidth(200);
|
||||||
name.setCellValueFactory(new PropertyValueFactory<JavaFxModel, String>("displayName"));
|
name.setCellValueFactory(new PropertyValueFactory<JavaFxModel, String>("displayName"));
|
||||||
|
name.setCellFactory(new ClickableCellFactory<>());
|
||||||
name.setEditable(false);
|
name.setEditable(false);
|
||||||
TableColumn<JavaFxModel, String> url = new TableColumn<>("URL");
|
TableColumn<JavaFxModel, String> url = new TableColumn<>("URL");
|
||||||
url.setCellValueFactory(new PropertyValueFactory<JavaFxModel, String>("url"));
|
url.setCellValueFactory(new PropertyValueFactory<JavaFxModel, String>("url"));
|
||||||
|
url.setCellFactory(new ClickableCellFactory<>());
|
||||||
url.setPrefWidth(400);
|
url.setPrefWidth(400);
|
||||||
url.setEditable(false);
|
url.setEditable(false);
|
||||||
TableColumn<JavaFxModel, Boolean> online = new TableColumn<>("Online");
|
TableColumn<JavaFxModel, Boolean> online = new TableColumn<>("Online");
|
||||||
|
@ -156,6 +168,14 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
paused.setCellFactory(CheckBoxTableCell.forTableColumn(paused));
|
paused.setCellFactory(CheckBoxTableCell.forTableColumn(paused));
|
||||||
paused.setPrefWidth(100);
|
paused.setPrefWidth(100);
|
||||||
paused.setEditable(true);
|
paused.setEditable(true);
|
||||||
|
TableColumn<JavaFxModel, Number> priority = new TableColumn<>("Priority");
|
||||||
|
priority.setCellValueFactory(param -> param.getValue().getPriorityProperty());
|
||||||
|
priority.setCellFactory(new PriorityCellFactory());
|
||||||
|
priority.setPrefWidth(90);
|
||||||
|
priority.setEditable(true);
|
||||||
|
priority.setOnEditStart(e -> cellEditing = true);
|
||||||
|
priority.setOnEditCommit(this::updatePriority);
|
||||||
|
priority.setOnEditCancel(e -> cellEditing = false);
|
||||||
TableColumn<JavaFxModel, String> notes = new TableColumn<>("Notes");
|
TableColumn<JavaFxModel, String> notes = new TableColumn<>("Notes");
|
||||||
notes.setCellValueFactory(cdf -> {
|
notes.setCellValueFactory(cdf -> {
|
||||||
JavaFxModel m = cdf.getValue();
|
JavaFxModel m = cdf.getValue();
|
||||||
|
@ -179,7 +199,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
});
|
});
|
||||||
notes.setPrefWidth(400);
|
notes.setPrefWidth(400);
|
||||||
notes.setEditable(false);
|
notes.setEditable(false);
|
||||||
table.getColumns().addAll(preview, name, url, online, recording, paused, notes);
|
table.getColumns().addAll(preview, name, url, online, recording, paused, priority, notes);
|
||||||
table.setItems(observableModels);
|
table.setItems(observableModels);
|
||||||
table.addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, event -> {
|
table.addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, event -> {
|
||||||
popup = createContextMenu();
|
popup = createContextMenu();
|
||||||
|
@ -188,14 +208,6 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
}
|
}
|
||||||
event.consume();
|
event.consume();
|
||||||
});
|
});
|
||||||
table.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
|
|
||||||
if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
|
|
||||||
JavaFxModel selectedModel = table.getSelectionModel().getSelectedItem();
|
|
||||||
if(selectedModel != null) {
|
|
||||||
new PlayAction(table, selectedModel).execute();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
table.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
|
table.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
|
||||||
if (popup != null) {
|
if (popup != null) {
|
||||||
popup.hide();
|
popup.hide();
|
||||||
|
@ -212,6 +224,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
pauseRecording(runningModels);
|
pauseRecording(runningModels);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
scrollPane.setContent(table);
|
scrollPane.setContent(table);
|
||||||
|
|
||||||
HBox addModelBox = new HBox(5);
|
HBox addModelBox = new HBox(5);
|
||||||
|
@ -264,6 +277,26 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
restoreState();
|
restoreState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updatePriority(CellEditEvent<JavaFxModel, Number> evt) {
|
||||||
|
try {
|
||||||
|
int prio = Optional.ofNullable(evt.getNewValue()).map(Number::intValue).orElse(-1);
|
||||||
|
if (prio < 0 || prio > 100) {
|
||||||
|
String msg = "Priority has to be between 0 and 100";
|
||||||
|
Dialogs.showError(table.getScene(), "Invalid value", msg, null);
|
||||||
|
} else {
|
||||||
|
evt.getRowValue().setPriority(prio);
|
||||||
|
try {
|
||||||
|
Config.getInstance().save();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.warn("Couldn't save updated priority value {} for {} - {}", evt.getNewValue(), evt.getRowValue().getName(), e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table.refresh();
|
||||||
|
} finally {
|
||||||
|
cellEditing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void addModel(ActionEvent e) {
|
private void addModel(ActionEvent e) {
|
||||||
String input = model.getText();
|
String input = model.getText();
|
||||||
if (StringUtil.isBlank(input)) {
|
if (StringUtil.isBlank(input)) {
|
||||||
|
@ -333,41 +366,28 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
void initializeUpdateService() {
|
void initializeUpdateService() {
|
||||||
updateService = createUpdateService();
|
updateService = createUpdateService();
|
||||||
updateService.setPeriod(new Duration(TimeUnit.SECONDS.toMillis(2)));
|
updateService.setPeriod(new Duration(TimeUnit.SECONDS.toMillis(2)));
|
||||||
updateService.setOnSucceeded((event) -> {
|
updateService.setOnSucceeded(this::onUpdateSuccess);
|
||||||
List<JavaFxModel> models = updateService.getValue();
|
updateService.setOnFailed(event -> LOG.info("Couldn't get list of models from recorder", event.getSource().getException()));
|
||||||
if (models == null) {
|
}
|
||||||
|
|
||||||
|
private void onUpdateSuccess(WorkerStateEvent event) {
|
||||||
|
if (cellEditing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<JavaFxModel> updatedModels = updateService.getValue();
|
||||||
|
if (updatedModels == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
for (JavaFxModel updatedModel : models) {
|
addOrUpdateModels(updatedModels);
|
||||||
int index = observableModels.indexOf(updatedModel);
|
|
||||||
if (index == -1) {
|
|
||||||
observableModels.add(updatedModel);
|
|
||||||
updatedModel.getPausedProperty().addListener((obs, oldV, newV) -> {
|
|
||||||
if (newV.booleanValue()) {
|
|
||||||
if(!recorder.isSuspended(updatedModel)) {
|
|
||||||
pauseRecording(Collections.singletonList(updatedModel));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(recorder.isSuspended(updatedModel)) {
|
|
||||||
resumeRecording(Collections.singletonList(updatedModel));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// make sure to update the JavaFX online property, so that the table cell is updated
|
|
||||||
JavaFxModel oldModel = observableModels.get(index);
|
|
||||||
oldModel.setSuspended(updatedModel.isSuspended());
|
|
||||||
oldModel.getOnlineProperty().set(updatedModel.getOnlineProperty().get());
|
|
||||||
oldModel.getRecordingProperty().set(updatedModel.getRecordingProperty().get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// remove old ones, which are not in the list of updated models
|
||||||
for (Iterator<JavaFxModel> iterator = observableModels.iterator(); iterator.hasNext();) {
|
for (Iterator<JavaFxModel> iterator = observableModels.iterator(); iterator.hasNext();) {
|
||||||
Model model = iterator.next();
|
Model oldModel = iterator.next();
|
||||||
if (!models.contains(model)) {
|
if (!updatedModels.contains(oldModel)) {
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -378,8 +398,36 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
filteredModels.clear();
|
filteredModels.clear();
|
||||||
filter(filter.getText());
|
filter(filter.getText());
|
||||||
table.sort();
|
table.sort();
|
||||||
});
|
}
|
||||||
updateService.setOnFailed(event -> LOG.info("Couldn't get list of models from recorder", event.getSource().getException()));
|
|
||||||
|
private void addOrUpdateModels(List<JavaFxModel> updatedModels) {
|
||||||
|
for (JavaFxModel updatedModel : updatedModels) {
|
||||||
|
int index = observableModels.indexOf(updatedModel);
|
||||||
|
if (index == -1) {
|
||||||
|
observableModels.add(updatedModel);
|
||||||
|
updatedModel.getPausedProperty().addListener(createPauseListener(updatedModel));
|
||||||
|
} else {
|
||||||
|
// make sure to update the JavaFX online property, so that the table cell is updated
|
||||||
|
JavaFxModel oldModel = observableModels.get(index);
|
||||||
|
oldModel.setSuspended(updatedModel.isSuspended());
|
||||||
|
oldModel.getOnlineProperty().set(updatedModel.getOnlineProperty().get());
|
||||||
|
oldModel.getRecordingProperty().set(updatedModel.getRecordingProperty().get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChangeListener<Boolean> createPauseListener(JavaFxModel updatedModel) {
|
||||||
|
return (obs, oldV, newV) -> {
|
||||||
|
if (newV.booleanValue()) {
|
||||||
|
if(!recorder.isSuspended(updatedModel)) {
|
||||||
|
pauseRecording(Collections.singletonList(updatedModel));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(recorder.isSuspended(updatedModel)) {
|
||||||
|
resumeRecording(Collections.singletonList(updatedModel));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void filter(String filter) {
|
private void filter(String filter) {
|
||||||
|
@ -398,7 +446,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
for (TableColumn<JavaFxModel, ?> tc : table.getColumns()) {
|
for (TableColumn<JavaFxModel, ?> tc : table.getColumns()) {
|
||||||
Object cellData = tc.getCellData(i);
|
Object cellData = tc.getCellData(i);
|
||||||
if(cellData != null) {
|
if (cellData != null) {
|
||||||
String content = cellData.toString();
|
String content = cellData.toString();
|
||||||
sb.append(content).append(' ');
|
sb.append(content).append(' ');
|
||||||
}
|
}
|
||||||
|
@ -407,14 +455,14 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
|
|
||||||
boolean tokensMissing = false;
|
boolean tokensMissing = false;
|
||||||
for (String token : tokens) {
|
for (String token : tokens) {
|
||||||
if(!searchText.toLowerCase().contains(token.toLowerCase())) {
|
if (!searchText.toLowerCase().contains(token.toLowerCase())) {
|
||||||
tokensMissing = true;
|
tokensMissing = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(tokensMissing) {
|
if (tokensMissing) {
|
||||||
JavaFxModel model = table.getItems().get(i);
|
JavaFxModel filteredModel = table.getItems().get(i);
|
||||||
filteredModels.add(model);
|
filteredModels.add(filteredModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
observableModels.removeAll(filteredModels);
|
observableModels.removeAll(filteredModels);
|
||||||
|
@ -424,7 +472,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScheduledService<List<JavaFxModel>> createUpdateService() {
|
private ScheduledService<List<JavaFxModel>> createUpdateService() {
|
||||||
ScheduledService<List<JavaFxModel>> updateService = new ScheduledService<List<JavaFxModel>>() {
|
ScheduledService<List<JavaFxModel>> modelUpdateService = new ScheduledService<List<JavaFxModel>>() {
|
||||||
@Override
|
@Override
|
||||||
protected Task<List<JavaFxModel>> createTask() {
|
protected Task<List<JavaFxModel>> createTask() {
|
||||||
return new Task<List<JavaFxModel>>() {
|
return new Task<List<JavaFxModel>>() {
|
||||||
|
@ -464,8 +512,8 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
t.setName("RecordedModelsTab UpdateService");
|
t.setName("RecordedModelsTab UpdateService");
|
||||||
return t;
|
return t;
|
||||||
});
|
});
|
||||||
updateService.setExecutor(executor);
|
modelUpdateService.setExecutor(executor);
|
||||||
return updateService;
|
return modelUpdateService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -489,10 +537,10 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
MenuItem stop = new MenuItem("Remove Model");
|
MenuItem stop = new MenuItem("Remove Model");
|
||||||
stop.setOnAction((e) -> stopAction(selectedModels));
|
stop.setOnAction(e -> stopAction(selectedModels));
|
||||||
|
|
||||||
MenuItem copyUrl = new MenuItem("Copy URL");
|
MenuItem copyUrl = new MenuItem("Copy URL");
|
||||||
copyUrl.setOnAction((e) -> {
|
copyUrl.setOnAction(e -> {
|
||||||
Model selected = selectedModels.get(0);
|
Model selected = selectedModels.get(0);
|
||||||
final Clipboard clipboard = Clipboard.getSystemClipboard();
|
final Clipboard clipboard = Clipboard.getSystemClipboard();
|
||||||
final ClipboardContent content = new ClipboardContent();
|
final ClipboardContent content = new ClipboardContent();
|
||||||
|
@ -645,4 +693,57 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ClickableCellFactory<S, T> implements Callback<TableColumn<S, T>, TableCell<S, T>> {
|
||||||
|
@Override
|
||||||
|
public TableCell<S, T> call(TableColumn<S, T> param) {
|
||||||
|
TableCell<S, T> cell = new TableCell<>() {
|
||||||
|
@Override
|
||||||
|
protected void updateItem(Object item, boolean empty) {
|
||||||
|
setText(empty ? "" : item.toString());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
cell.addEventFilter(MouseEvent.MOUSE_CLICKED, event -> {
|
||||||
|
if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
|
||||||
|
JavaFxModel selectedModel = table.getSelectionModel().getSelectedItem();
|
||||||
|
if(selectedModel != null) {
|
||||||
|
new PlayAction(table, selectedModel).execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PriorityCellFactory implements Callback<TableColumn<JavaFxModel, Number>, TableCell<JavaFxModel, Number>> {
|
||||||
|
@Override
|
||||||
|
public TableCell<JavaFxModel, Number> call(TableColumn<JavaFxModel, Number> param) {
|
||||||
|
Callback<TableColumn<JavaFxModel, Number>, TableCell<JavaFxModel, Number>> callback = TextFieldTableCell
|
||||||
|
.<JavaFxModel, Number> forTableColumn((StringConverter<Number>) new NumberStringConverter());
|
||||||
|
TableCell<JavaFxModel, Number> tableCell = callback.call(param);
|
||||||
|
|
||||||
|
tableCell.setOnScroll(event -> {
|
||||||
|
if(event.isControlDown()) {
|
||||||
|
event.consume();
|
||||||
|
JavaFxModel m = tableCell.getTableRow().getItem();
|
||||||
|
int prio = m.getPriority();
|
||||||
|
if(event.getDeltaY() < 0) {
|
||||||
|
prio--;
|
||||||
|
} else {
|
||||||
|
prio++;
|
||||||
|
}
|
||||||
|
prio = Math.min(Math.max(0, prio), 100);
|
||||||
|
m.setPriority(prio);
|
||||||
|
try {
|
||||||
|
Config.getInstance().save();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.warn("Couldn't save updated priority value {} for {} - {}", prio, m.getName(), e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return tableCell;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,8 +23,9 @@ public abstract class AbstractModel implements Model {
|
||||||
private String description;
|
private String description;
|
||||||
private List<String> tags = new ArrayList<>();
|
private List<String> tags = new ArrayList<>();
|
||||||
private int streamUrlIndex = -1;
|
private int streamUrlIndex = -1;
|
||||||
|
private int priority = 50;
|
||||||
private boolean suspended = false;
|
private boolean suspended = false;
|
||||||
protected Site site;
|
protected transient Site site;
|
||||||
protected State onlineState = State.UNKNOWN;
|
protected State onlineState = State.UNKNOWN;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -175,7 +176,7 @@ public abstract class AbstractModel implements Model {
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(Model o) {
|
public int compareTo(Model o) {
|
||||||
String thisName = Optional.ofNullable(getDisplayName()).orElse("").toLowerCase();
|
String thisName = Optional.ofNullable(getDisplayName()).orElse("").toLowerCase();
|
||||||
String otherName = Optional.ofNullable(o).map(m -> m.getDisplayName()).orElse("").toLowerCase();
|
String otherName = Optional.ofNullable(o).map(Model::getDisplayName).orElse("").toLowerCase();
|
||||||
return thisName.compareTo(otherName);
|
return thisName.compareTo(otherName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,6 +195,16 @@ public abstract class AbstractModel implements Model {
|
||||||
return site;
|
return site;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPriority() {
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPriority(int priority) {
|
||||||
|
this.priority = priority;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Download createDownload() {
|
public Download createDownload() {
|
||||||
if(Config.isServerMode()) {
|
if(Config.isServerMode()) {
|
||||||
|
|
|
@ -112,4 +112,8 @@ public interface Model extends Comparable<Model>, Serializable {
|
||||||
|
|
||||||
public Download createDownload();
|
public Download createDownload();
|
||||||
|
|
||||||
|
public void setPriority(int priority);
|
||||||
|
|
||||||
|
public int getPriority();
|
||||||
|
|
||||||
}
|
}
|
|
@ -19,7 +19,7 @@ import ctbrec.sites.chaturbate.ChaturbateModel;
|
||||||
|
|
||||||
public class ModelJsonAdapter extends JsonAdapter<Model> {
|
public class ModelJsonAdapter extends JsonAdapter<Model> {
|
||||||
|
|
||||||
private static final transient Logger LOG = LoggerFactory.getLogger(ModelJsonAdapter.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ModelJsonAdapter.class);
|
||||||
|
|
||||||
private List<Site> sites;
|
private List<Site> sites;
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ public class ModelJsonAdapter extends JsonAdapter<Model> {
|
||||||
String url = null;
|
String url = null;
|
||||||
Object type = null;
|
Object type = null;
|
||||||
int streamUrlIndex = -1;
|
int streamUrlIndex = -1;
|
||||||
|
int priority;
|
||||||
boolean suspended = false;
|
boolean suspended = false;
|
||||||
|
|
||||||
Model model = null;
|
Model model = null;
|
||||||
|
@ -46,7 +47,11 @@ public class ModelJsonAdapter extends JsonAdapter<Model> {
|
||||||
Token token = reader.peek();
|
Token token = reader.peek();
|
||||||
if(token == Token.NAME) {
|
if(token == Token.NAME) {
|
||||||
String key = reader.nextName();
|
String key = reader.nextName();
|
||||||
if(key.equals("name")) {
|
if(key.equals("type")) {
|
||||||
|
type = reader.readJsonValue();
|
||||||
|
Class<?> modelClass = Class.forName(Optional.ofNullable(type).orElse(ChaturbateModel.class.getName()).toString());
|
||||||
|
model = (Model) modelClass.getDeclaredConstructor().newInstance();
|
||||||
|
} else if(key.equals("name")) {
|
||||||
name = reader.nextString();
|
name = reader.nextString();
|
||||||
model.setName(name);
|
model.setName(name);
|
||||||
} else if(key.equals("description")) {
|
} else if(key.equals("description")) {
|
||||||
|
@ -55,10 +60,9 @@ public class ModelJsonAdapter extends JsonAdapter<Model> {
|
||||||
} else if(key.equals("url")) {
|
} else if(key.equals("url")) {
|
||||||
url = reader.nextString();
|
url = reader.nextString();
|
||||||
model.setUrl(url);
|
model.setUrl(url);
|
||||||
} else if(key.equals("type")) {
|
} else if(key.equals("priority")) {
|
||||||
type = reader.readJsonValue();
|
priority = reader.nextInt();
|
||||||
Class<?> modelClass = Class.forName(Optional.ofNullable(type).orElse(ChaturbateModel.class.getName()).toString());
|
model.setPriority(priority);
|
||||||
model = (Model) modelClass.getDeclaredConstructor().newInstance();
|
|
||||||
} else if(key.equals("streamUrlIndex")) {
|
} else if(key.equals("streamUrlIndex")) {
|
||||||
streamUrlIndex = reader.nextInt();
|
streamUrlIndex = reader.nextInt();
|
||||||
model.setStreamUrlIndex(streamUrlIndex);
|
model.setStreamUrlIndex(streamUrlIndex);
|
||||||
|
@ -69,7 +73,7 @@ public class ModelJsonAdapter extends JsonAdapter<Model> {
|
||||||
reader.beginObject();
|
reader.beginObject();
|
||||||
try {
|
try {
|
||||||
model.readSiteSpecificData(reader);
|
model.readSiteSpecificData(reader);
|
||||||
} catch(Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Couldn't read site specific data for model {}", model.getName());
|
LOG.error("Couldn't read site specific data for model {}", model.getName());
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
@ -101,6 +105,7 @@ public class ModelJsonAdapter extends JsonAdapter<Model> {
|
||||||
writeValueIfSet(writer, "name", model.getName());
|
writeValueIfSet(writer, "name", model.getName());
|
||||||
writeValueIfSet(writer, "description", model.getDescription());
|
writeValueIfSet(writer, "description", model.getDescription());
|
||||||
writeValueIfSet(writer, "url", model.getUrl());
|
writeValueIfSet(writer, "url", model.getUrl());
|
||||||
|
writer.name("priority").value(model.getPriority());
|
||||||
writer.name("streamUrlIndex").value(model.getStreamUrlIndex());
|
writer.name("streamUrlIndex").value(model.getStreamUrlIndex());
|
||||||
writer.name("suspended").value(model.isSuspended());
|
writer.name("suspended").value(model.isSuspended());
|
||||||
writer.name("siteSpecific");
|
writer.name("siteSpecific");
|
||||||
|
|
|
@ -237,9 +237,24 @@ public class NextGenLocalRecorder implements Recorder {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!downloadSlotAvailable()) {
|
if (!downloadSlotAvailable()) {
|
||||||
LOG.info("The number of downloads is maxed out, not starting recording for {}", model);
|
long now = System.currentTimeMillis();
|
||||||
|
if ((now - lastSpaceMessage) > TimeUnit.MINUTES.toMillis(1)) {
|
||||||
|
LOG.info("The number of downloads is maxed out");
|
||||||
|
}
|
||||||
|
// check, if we can stop a recording for a model with lower priority
|
||||||
|
Optional<Recording> lowerPrioRecordingProcess = recordingProcessWithLowerPrio(model.getPriority());
|
||||||
|
if (lowerPrioRecordingProcess.isPresent()) {
|
||||||
|
Download download = lowerPrioRecordingProcess.get().getDownload();
|
||||||
|
Model lowerPrioModel = download.getModel();
|
||||||
|
LOG.info("Stopping recording for {}. Prio {} < {}", lowerPrioModel.getName(), lowerPrioModel.getPriority(), model.getPriority());
|
||||||
|
stopRecordingProcess(lowerPrioModel);
|
||||||
|
} else {
|
||||||
|
if ((now - lastSpaceMessage) > TimeUnit.MINUTES.toMillis(1)) {
|
||||||
|
LOG.info("Other models have higher prio, not starting recording for {}", model.getName());
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LOG.info("Starting recording for model {}", model.getName());
|
LOG.info("Starting recording for model {}", model.getName());
|
||||||
Download download = model.createDownload();
|
Download download = model.createDownload();
|
||||||
|
@ -276,6 +291,20 @@ public class NextGenLocalRecorder implements Recorder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Optional<Recording> recordingProcessWithLowerPrio(int priority) {
|
||||||
|
Model lowest = null;
|
||||||
|
for (Model m : recordingProcesses.keySet()) {
|
||||||
|
if (lowest == null || m.getPriority() < lowest.getPriority()) {
|
||||||
|
lowest = m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lowest != null && lowest.getPriority() < priority) {
|
||||||
|
return Optional.of(recordingProcesses.get(lowest));
|
||||||
|
} else {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean deleteIfEmpty(Recording rec) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
|
private boolean deleteIfEmpty(Recording rec) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
|
||||||
rec.refresh();
|
rec.refresh();
|
||||||
long sizeInByte = rec.getSizeInByte();
|
long sizeInByte = rec.getSizeInByte();
|
||||||
|
|
Loading…
Reference in New Issue