Add first configurable version of the notification system

This commit is contained in:
0xboobface 2018-12-09 18:46:33 +01:00
parent be680a07f9
commit 888046676f
23 changed files with 838 additions and 150 deletions

View File

@ -1,9 +1,6 @@
package ctbrec.ui;
import static ctbrec.Model.State.*;
import static ctbrec.event.Event.Type.*;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
@ -19,18 +16,16 @@ import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.eventbus.Subscribe;
import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.Moshi;
import com.squareup.moshi.Types;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.StringUtil;
import ctbrec.Version;
import ctbrec.event.Event;
import ctbrec.event.EventBusHolder;
import ctbrec.event.ModelStateChangedEvent;
import ctbrec.event.EventHandler;
import ctbrec.event.EventHandlerConfiguration;
import ctbrec.io.HttpClient;
import ctbrec.recorder.LocalRecorder;
import ctbrec.recorder.OnlineMonitor;
@ -62,7 +57,6 @@ public class CamrecApplication extends Application {
static final transient Logger LOG = LoggerFactory.getLogger(CamrecApplication.class);
private Stage primaryStage;
private Config config;
private Recorder recorder;
private OnlineMonitor onlineMonitor;
@ -75,15 +69,14 @@ public class CamrecApplication extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
this.primaryStage = primaryStage;
logEnvironment();
registerAlertSystem();
sites.add(new BongaCams());
sites.add(new Cam4());
sites.add(new Camsoda());
sites.add(new Chaturbate());
sites.add(new MyFreeCams());
loadConfig();
registerAlertSystem();
createHttpClient();
hostServices = getHostServices();
createRecorder();
@ -101,7 +94,6 @@ public class CamrecApplication extends Application {
}
createGui(primaryStage);
checkForUpdates();
}
private void logEnvironment() {
@ -134,7 +126,7 @@ public class CamrecApplication extends Application {
rootPane.getTabs().add(modelsTab);
RecordingsTab recordingsTab = new RecordingsTab("Recordings", recorder, config, sites);
rootPane.getTabs().add(recordingsTab);
settingsTab = new SettingsTab(sites);
settingsTab = new SettingsTab(sites, recorder);
rootPane.getTabs().add(settingsTab);
rootPane.getTabs().add(new DonateTabFx());
@ -222,38 +214,11 @@ public class CamrecApplication extends Application {
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
EventBusHolder.BUS.register(new Object() {
@Subscribe
public void modelEvent(Event e) {
try {
if (e.getType() == MODEL_STATUS_CHANGED) {
ModelStateChangedEvent evt = (ModelStateChangedEvent) e;
Model model = evt.getModel();
if (evt.getNewState() == ONLINE && primaryStage != null && primaryStage.getTitle() != null) {
String header = "Model Online";
String msg = model.getDisplayName() + " is now online";
LOG.debug(msg);
//OS.notification(primaryStage.getTitle(), header, msg);
}
}
} catch (Exception e1) {
LOG.error("Couldn't show notification", e1);
}
}
});
// EventBusHolder.BUS.register(new Object() {
// URL url = CamrecApplication.class.getResource("/Oxygen-Im-Highlight-Msg.mp3");
// PlaySound playSound = new PlaySound(url);
// EventHandler reaction = new EventHandler(playSound);
// // LogReaction reaction = new LogReaction();
// @Subscribe
// public void modelEvent(Event e) {
// reaction.reactToEvent(e);
// }
// });
for (EventHandlerConfiguration config : Config.getInstance().getSettings().eventHandlers) {
EventHandler handler = new EventHandler(config);
EventBusHolder.register(handler);
LOG.debug("Registered event handler for {} {}", config.getEvent(), config.getName());
}
LOG.debug("Alert System registered");
}

View File

@ -207,4 +207,9 @@ public class JavaFxModel implements Model {
public void setDisplayName(String name) {
delegate.setDisplayName(name);
}
@Override
public int compareTo(Model o) {
return delegate.compareTo(o);
}
}

View File

@ -1,5 +1,8 @@
package ctbrec.ui.controls;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
@ -11,6 +14,7 @@ import javafx.stage.Stage;
public class Wizard extends BorderPane {
private static final transient Logger LOG = LoggerFactory.getLogger(Wizard.class);
private Pane[] pages;
private StackPane stack;
private Stage stage;
@ -18,6 +22,7 @@ public class Wizard extends BorderPane {
private Button next;
private Button prev;
private Button finish;
private boolean cancelled = true;
public Wizard(Stage stage, Pane... pages) {
this.stage = stage;
@ -33,42 +38,55 @@ public class Wizard extends BorderPane {
private void createUi() {
stack = new StackPane();
setCenter(stack);
next = new Button("Next");
next.setOnAction(evt -> nextPage());
prev = new Button("Prev");
prev = new Button("Back");
prev.setOnAction(evt -> prevPage());
prev.visibleProperty().bind(next.visibleProperty());
next.setVisible(pages.length > 1);
Button cancel = new Button("Cancel");
cancel.setOnAction(evt -> stage.close());
finish = new Button("Finish");
finish.setOnAction(evt -> {
cancelled = false;
stage.close();
});
HBox buttons = new HBox(5, prev, next, cancel, finish);
buttons.setAlignment(Pos.BASELINE_RIGHT);
setBottom(buttons);
BorderPane.setMargin(buttons, new Insets(5));
BorderPane.setMargin(buttons, new Insets(10));
if (pages.length != 0) {
stack.getChildren().add(pages[0]);
prevPage();
}
setButtonStates();
}
private void prevPage() {
page = Math.max(0, --page);
stack.getChildren().clear();
stack.getChildren().add(pages[page]);
setButtonStates();
updateState();
}
private void nextPage() {
page = Math.min(pages.length - 1, ++page);
stack.getChildren().clear();
stack.getChildren().add(pages[page]);
setButtonStates();
updateState();
}
private void setButtonStates() {
private void updateState() {
prev.setDisable(page == 0);
next.setDisable(page == pages.length - 1);
finish.setDisable(page != pages.length - 1);
LOG.debug("Setting border");
pages[page].setStyle(
"-fx-background-color: -fx-inner-border, -fx-background;"+
"-fx-background-insets: 0 0 -1 0, 0, 1, 2;");
}
public boolean isCancelled() {
return cancelled;
}
}

View File

@ -1,23 +0,0 @@
package ctbrec.ui.event;
import ctbrec.OS;
import ctbrec.event.Action;
import ctbrec.event.Event;
import ctbrec.ui.CamrecApplication;
public class ModelStateNotification extends Action {
private String header;
private String msg;
public ModelStateNotification(String header, String msg) {
this.header = header;
this.msg = msg;
name = "show notification";
}
@Override
public void accept(Event evt) {
OS.notification(CamrecApplication.title, header, msg);
}
}

View File

@ -1,15 +1,19 @@
package ctbrec.ui.event;
import java.io.File;
import java.net.URL;
import ctbrec.event.Action;
import ctbrec.event.Event;
import ctbrec.event.EventHandlerConfiguration.ActionConfiguration;
import javafx.scene.media.AudioClip;
public class PlaySound extends Action {
private URL url;
public PlaySound() {}
public PlaySound(URL url) {
this.url = url;
name = "play sound";
@ -20,4 +24,10 @@ public class PlaySound extends Action {
AudioClip clip = new AudioClip(url.toString());
clip.play();
}
@Override
public void configure(ActionConfiguration config) throws Exception {
File file = new File((String) config.getConfiguration().get("file"));
url = file.toURI().toURL();
}
}

View File

@ -0,0 +1,42 @@
package ctbrec.ui.event;
import ctbrec.Model;
import ctbrec.OS;
import ctbrec.event.Action;
import ctbrec.event.Event;
import ctbrec.event.EventHandlerConfiguration.ActionConfiguration;
import ctbrec.event.ModelStateChangedEvent;
import ctbrec.event.RecordingStateChangedEvent;
import ctbrec.ui.CamrecApplication;
public class ShowNotification extends Action {
public ShowNotification() {
name = "show notification";
}
@Override
public void accept(Event evt) {
String header = evt.getType().toString();
String msg;
switch(evt.getType()) {
case MODEL_STATUS_CHANGED:
ModelStateChangedEvent modelEvent = (ModelStateChangedEvent) evt;
Model m = modelEvent.getModel();
msg = m.getDisplayName() + " is now " + modelEvent.getNewState().toString();
break;
case RECORDING_STATUS_CHANGED:
RecordingStateChangedEvent recEvent = (RecordingStateChangedEvent) evt;
m = recEvent.getModel();
msg = "Recording for model " + m.getDisplayName() + " is now in state " + recEvent.getState().toString();
break;
default:
msg = evt.getDescription();
}
OS.notification(CamrecApplication.title, header, msg);
}
@Override
public void configure(ActionConfiguration config) throws Exception {
}
}

View File

@ -1,35 +1,95 @@
package ctbrec.ui.settings;
import java.io.File;
import java.io.InputStream;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.OS;
import ctbrec.Recording;
import ctbrec.event.Event;
import ctbrec.event.EventBusHolder;
import ctbrec.event.EventHandler;
import ctbrec.event.EventHandlerConfiguration;
import ctbrec.event.EventHandlerConfiguration.ActionConfiguration;
import ctbrec.event.EventHandlerConfiguration.PredicateConfiguration;
import ctbrec.event.ExecuteProgram;
import ctbrec.event.ModelPredicate;
import ctbrec.event.ModelStatePredicate;
import ctbrec.event.RecordingStatePredicate;
import ctbrec.recorder.Recorder;
import ctbrec.ui.CamrecApplication;
import ctbrec.ui.controls.FileSelectionBox;
import ctbrec.ui.controls.ProgramSelectionBox;
import ctbrec.ui.controls.Wizard;
import ctbrec.ui.event.PlaySound;
import ctbrec.ui.event.ShowNotification;
import javafx.collections.ListChangeListener;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TableView;
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.Border;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
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);
private ListView<EventHandlerConfiguration> actionTable;
private TableView actionTable;
private TextField name = new TextField();
private ComboBox<Event.Type> event = new ComboBox<>();
private ComboBox<Model.State> modelState = new ComboBox<>();
private ComboBox<Recording.State> recordingState = new ComboBox<>();
public ActionSettingsPanel(SettingsTab settingsTab) {
private CheckBox playSound = new CheckBox("Play sound");
private FileSelectionBox sound = new FileSelectionBox();
private CheckBox showNotification = new CheckBox("Notify me");
private Button testNotification = new Button("Test");
private CheckBox executeProgram = new CheckBox("Execute program");
private ProgramSelectionBox program = new ProgramSelectionBox();
private ListSelectionPane<Model> modelSelectionPane;
private Recorder recorder;
public ActionSettingsPanel(SettingsTab settingsTab, Recorder recorder) {
this.recorder = recorder;
setText("Events & Actions");
setExpanded(true);
setCollapsible(false);
createGui();
loadEventHandlers();
}
private void loadEventHandlers() {
actionTable.getItems().addAll(Config.getInstance().getSettings().eventHandlers);
}
private void createGui() {
@ -37,58 +97,202 @@ public class ActionSettingsPanel extends TitledPane {
setContent(mainLayout);
actionTable = createActionTable();
actionTable.setPrefSize(300, 200);
ScrollPane scrollPane = new ScrollPane(actionTable);
scrollPane.setFitToHeight(true);
scrollPane.setFitToWidth(true);
scrollPane.setBorder(Border.EMPTY);
scrollPane.setStyle("-fx-background-color: -fx-background");
mainLayout.setCenter(scrollPane);
BorderPane.setMargin(scrollPane, new Insets(5));
Button add = new Button("Add");
add.setOnAction(this::add);
Button delete = new Button("Delete");
delete.setOnAction(this::delete);
delete.setDisable(true);
HBox buttons = new HBox(10, add, delete);
HBox buttons = new HBox(5, add, delete);
mainLayout.setBottom(buttons);
BorderPane.setMargin(buttons, new Insets(5));
BorderPane.setMargin(buttons, new Insets(5, 0, 0, 0));
actionTable.getSelectionModel().getSelectedItems().addListener(new ListChangeListener<EventHandlerConfiguration>() {
@Override
public void onChanged(Change<? extends EventHandlerConfiguration> change) {
delete.setDisable(change.getList().isEmpty());
}
});
}
private void add(ActionEvent evt) {
EventHandlerConfiguration config = new EventHandlerConfiguration();
Pane namePane = createNamePane(config);
GridPane pane2 = SettingsTab.createGridLayout();
pane2.add(new Label("Pane 2"), 0, 0);
GridPane pane3 = SettingsTab.createGridLayout();
pane3.add(new Label("Pane 3"), 0, 0);
Pane actionPane = createActionPane();
Stage dialog = new Stage();
dialog.initModality(Modality.APPLICATION_MODAL);
dialog.initOwner(getScene().getWindow());
dialog.setTitle("New Action");
InputStream icon = getClass().getResourceAsStream("/icon.png");
dialog.getIcons().add(new Image(icon));
Wizard root = new Wizard(dialog, namePane, pane2, pane3);
Scene scene = new Scene(root, 640, 480);
Wizard root = new Wizard(dialog, actionPane);
Scene scene = new Scene(root, 800, 540);
scene.getStylesheets().addAll(getScene().getStylesheets());
dialog.setScene(scene);
centerOnParent(dialog);
dialog.showAndWait();
if(!root.isCancelled()) {
createEventHandler();
}
}
private void createEventHandler() {
EventHandlerConfiguration config = new EventHandlerConfiguration();
config.setName(name.getText());
config.setEvent(event.getValue());
if(event.getValue() == Event.Type.MODEL_STATUS_CHANGED) {
PredicateConfiguration pc = new PredicateConfiguration();
pc.setType(ModelStatePredicate.class.getName());
pc.getConfiguration().put("state", modelState.getValue().name());
pc.setName("state = " + modelState.getValue().toString());
config.getPredicates().add(pc);
} else if(event.getValue() == Event.Type.RECORDING_STATUS_CHANGED) {
PredicateConfiguration pc = new PredicateConfiguration();
pc.setType(RecordingStatePredicate.class.getName());
pc.getConfiguration().put("state", recordingState.getValue().name());
pc.setName("state = " + recordingState.getValue().toString());
config.getPredicates().add(pc);
}
if(!modelSelectionPane.isAllSelected()) {
PredicateConfiguration pc = new PredicateConfiguration();
pc.setType(ModelPredicate.class.getName());
pc.setModels(modelSelectionPane.getSelectedItems());
pc.setName("model is one of:" + modelSelectionPane.getSelectedItems());
config.getPredicates().add(pc);
}
if(showNotification.isSelected()) {
ActionConfiguration ac = new ActionConfiguration();
ac.setType(ShowNotification.class.getName());
ac.setName("show notification");
config.getActions().add(ac);
}
if(playSound.isSelected()) {
ActionConfiguration ac = new ActionConfiguration();
ac.setType(PlaySound.class.getName());
File file = sound.fileProperty().get();
ac.getConfiguration().put("file", file.getAbsolutePath());
ac.setName("play " + file.getName());
config.getActions().add(ac);
}
if(executeProgram.isSelected()) {
ActionConfiguration ac = new ActionConfiguration();
ac.setType(ExecuteProgram.class.getName());
File file = program.fileProperty().get();
ac.getConfiguration().put("file", file.getAbsolutePath());
ac.setName("execute " + file.getName());
config.getActions().add(ac);
}
EventHandler handler = new EventHandler(config);
EventBusHolder.register(handler);
Config.getInstance().getSettings().eventHandlers.add(config);
actionTable.getItems().add(config);
LOG.debug("Registered event handler for {} {}", config.getEvent(), config.getName());
}
private void delete(ActionEvent evt) {
List<EventHandlerConfiguration> selected = new ArrayList<>(actionTable.getSelectionModel().getSelectedItems());
for (EventHandlerConfiguration config : selected) {
EventBusHolder.unregister(config.getId());
Config.getInstance().getSettings().eventHandlers.remove(config);
actionTable.getItems().remove(config);
}
}
private Pane createNamePane(EventHandlerConfiguration config) {
private Pane createActionPane() {
GridPane layout = SettingsTab.createGridLayout();
recordingState.prefWidthProperty().bind(event.widthProperty());
modelState.prefWidthProperty().bind(event.widthProperty());
name.prefWidthProperty().bind(event.widthProperty());
int row = 0;
layout.add(new Label("Name"), 0, row);
TextField name = new TextField();
layout.add(name, 1, row);
layout.add(name, 1, row++);
layout.add(new Label("Event"), 0, row);
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.getSelectionModel().select(Event.Type.MODEL_STATUS_CHANGED);
layout.add(event, 1, row++);
layout.add(new Label("State"), 0, row);
modelState.getItems().addAll(Model.State.values());
layout.add(modelState, 1, row);
recordingState.getItems().addAll(Recording.State.values());
layout.add(recordingState, 1, row++);
recordingState.visibleProperty().bind(modelState.visibleProperty().not());
layout.add(createSeparator(), 0, row++);
Label l = new Label("Models");
layout.add(l, 0, row);
modelSelectionPane = new ListSelectionPane<Model>(recorder.getModelsRecording(), Collections.emptyList());
layout.add(modelSelectionPane, 1, row++);
GridPane.setValignment(l, VPos.TOP);
GridPane.setHgrow(modelSelectionPane, Priority.ALWAYS);
GridPane.setFillWidth(modelSelectionPane, true);
layout.add(createSeparator(), 0, row++);
layout.add(showNotification, 0, row);
layout.add(testNotification, 1, row++);
testNotification.setOnAction(evt -> {
DateTimeFormatter format = DateTimeFormatter.ofLocalizedTime(FormatStyle.MEDIUM);
ZonedDateTime time = ZonedDateTime.now();
OS.notification(CamrecApplication.title, "Test Notification", "Oi, what's up! " + format.format(time));
});
testNotification.disableProperty().bind(showNotification.selectedProperty().not());
layout.add(playSound, 0, row);
layout.add(sound, 1, row++);
sound.disableProperty().bind(playSound.selectedProperty().not());
layout.add(executeProgram, 0, row);
layout.add(program, 1, row);
program.disableProperty().bind(executeProgram.selectedProperty().not());
GridPane.setFillWidth(name, true);
GridPane.setHgrow(name, Priority.ALWAYS);
GridPane.setFillWidth(sound, true);
return layout;
}
private TableView createActionTable() {
TableView view = new TableView();
private ListView<EventHandlerConfiguration> createActionTable() {
ListView<EventHandlerConfiguration> view = new ListView<>();
view.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
view.setPrefSize(300, 200);
return view;
}
private Node createSeparator() {
Separator divider = new Separator(Orientation.HORIZONTAL);
GridPane.setHgrow(divider, Priority.ALWAYS);
GridPane.setFillWidth(divider, true);
GridPane.setColumnSpan(divider, 2);
int tb = 20;
int lr = 0;
GridPane.setMargin(divider, new Insets(tb, lr, tb, lr));
return divider;
}
private void centerOnParent(Stage dialog) {
dialog.setWidth(dialog.getScene().getWidth());
dialog.setHeight(dialog.getScene().getHeight());
double w = dialog.getWidth();
double h = dialog.getHeight();
Window p = dialog.getOwner();
double px = p.getX();
double py = p.getY();
double pw = p.getWidth();
double ph = p.getHeight();
dialog.setX(px + (pw - w) / 2);
dialog.setY(py + (ph - h) / 2);
}
}

View File

@ -0,0 +1,121 @@
package ctbrec.ui.settings;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.SelectionMode;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
public class ListSelectionPane<T extends Comparable<T>> extends GridPane {
private ListView<T> availableListView = new ListView<>();
private ListView<T> selectedListView = new ListView<>();
private Button addModel = new Button(">");
private Button removeModel = new Button("<");
private CheckBox selectAll = new CheckBox("all");
public ListSelectionPane(List<T> available, List<T> selected) {
super();
setHgap(5);
setVgap(5);
createGui();
fillLists(available, selected);
}
private void fillLists(List<T> available, List<T> selected) {
ObservableList<T> obsAvail = FXCollections.observableArrayList(available);
ObservableList<T> obsSel = FXCollections.observableArrayList(selected);
for (Iterator<T> iterator = obsAvail.iterator(); iterator.hasNext();) {
T t = iterator.next();
if(obsSel.contains(t)) {
iterator.remove();
}
}
Collections.sort(obsAvail);
Collections.sort(obsSel);
availableListView.setItems(obsAvail);
selectedListView.setItems(obsSel);
availableListView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
selectedListView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
}
private void createGui() {
Label labelAvailable = new Label("Available");
Label labelSelected = new Label("Selected");
add(labelAvailable, 0, 0);
add(availableListView, 0, 1);
VBox buttonBox = new VBox(5);
buttonBox.getChildren().add(addModel);
buttonBox.getChildren().add(removeModel);
buttonBox.setAlignment(Pos.CENTER);
add(buttonBox, 1, 1);
add(labelSelected, 2, 0);
add(selectedListView, 2, 1);
add(selectAll, 0, 2);
GridPane.setHgrow(availableListView, Priority.ALWAYS);
GridPane.setHgrow(selectedListView, Priority.ALWAYS);
GridPane.setFillWidth(availableListView, true);
GridPane.setFillWidth(selectedListView, true);
addModel.setOnAction(evt -> addSelectedItems());
removeModel.setOnAction(evt -> removeSelectedItems());
availableListView.disableProperty().bind(selectAll.selectedProperty());
selectedListView.disableProperty().bind(selectAll.selectedProperty());
addModel.disableProperty().bind(selectAll.selectedProperty());
removeModel.disableProperty().bind(selectAll.selectedProperty());
}
private void addSelectedItems() {
List<T> selected = new ArrayList<>(availableListView.getSelectionModel().getSelectedItems());
for (T t : selected) {
if(!selectedListView.getItems().contains(t)) {
selectedListView.getItems().add(t);
availableListView.getItems().remove(t);
}
}
Collections.sort(selectedListView.getItems());
}
private void removeSelectedItems() {
List<T> selected = new ArrayList<>(selectedListView.getSelectionModel().getSelectedItems());
for (T t : selected) {
if(!availableListView.getItems().contains(t)) {
availableListView.getItems().add(t);
selectedListView.getItems().remove(t);
}
}
Collections.sort(availableListView.getItems());
}
public List<T> getSelectedItems() {
if(selectAll.isSelected()) {
List<T> all = new ArrayList<>(availableListView.getItems());
all.addAll(selectedListView.getItems());
return all;
} else {
return selectedListView.getItems();
}
}
public boolean isAllSelected() {
return selectAll.isSelected();
}
}

View File

@ -15,6 +15,7 @@ import ctbrec.Config;
import ctbrec.Hmac;
import ctbrec.Settings.DirectoryStructure;
import ctbrec.StringUtil;
import ctbrec.recorder.Recorder;
import ctbrec.sites.ConfigUI;
import ctbrec.sites.Site;
import ctbrec.ui.SiteUiFactory;
@ -73,9 +74,11 @@ public class SettingsTab extends Tab implements TabSelectionListener {
private List<Site> sites;
private Label restartLabel;
private Accordion siteConfigAccordion = new Accordion();
private Recorder recorder;
public SettingsTab(List<Site> sites) {
public SettingsTab(List<Site> sites, Recorder recorder) {
this.sites = sites;
this.recorder = recorder;
setText("Settings");
createGui();
setClosable(false);
@ -125,7 +128,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
//right side
rightSide.getChildren().add(siteConfigAccordion);
ActionSettingsPanel actions = new ActionSettingsPanel(this);
ActionSettingsPanel actions = new ActionSettingsPanel(this, recorder);
rightSide.getChildren().add(actions);
proxySettingsPane = new ProxySettingsPane(this);
rightSide.getChildren().add(proxySettingsPane);

View File

@ -3,6 +3,7 @@ package ctbrec;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import com.squareup.moshi.JsonReader;
@ -162,6 +163,13 @@ public abstract class AbstractModel implements Model {
return true;
}
@Override
public int compareTo(Model o) {
String thisName = Optional.ofNullable(getDisplayName()).orElse("").toLowerCase();
String otherName = Optional.ofNullable(o).map(m -> m.getDisplayName()).orElse("").toLowerCase();
return thisName.compareTo(otherName);
}
@Override
public String toString() {
return getName();

View File

@ -12,7 +12,7 @@ import com.squareup.moshi.JsonWriter;
import ctbrec.recorder.download.StreamSource;
import ctbrec.sites.Site;
public interface Model {
public interface Model extends Comparable<Model> {
public static enum State {
ONLINE("online"),

View File

@ -4,6 +4,8 @@ import java.io.File;
import java.util.ArrayList;
import java.util.List;
import ctbrec.event.EventHandlerConfiguration;
public class Settings {
public enum ProxyType {
@ -56,7 +58,8 @@ public class Settings {
public String cam4Password;
public String lastDownloadDir = "";
public List<Model> models = new ArrayList<Model>();
public List<Model> models = new ArrayList<>();
public List<EventHandlerConfiguration> eventHandlers = new ArrayList<>();
public boolean determineResolution = false;
public boolean requireAuthentication = false;
public boolean chooseStreamQuality = false;

View File

@ -2,6 +2,8 @@ package ctbrec.event;
import java.util.function.Consumer;
import ctbrec.event.EventHandlerConfiguration.ActionConfiguration;
public abstract class Action implements Consumer<Event> {
protected String name;
@ -13,4 +15,11 @@ public abstract class Action implements Consumer<Event> {
public void setName(String name) {
this.name = name;
}
public abstract void configure(ActionConfiguration config) throws Exception;
@Override
public String toString() {
return name;
}
}

View File

@ -1,16 +1,37 @@
package ctbrec.event;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.EventBus;
public class EventBusHolder {
public static final String EVENT = "event";
public static final String OLD = "old";
public static final String STATUS = "status";
public static final String MODEL = "model";
private static final transient Logger LOG = LoggerFactory.getLogger(EventBusHolder.class);
private static Map<String, EventHandler> handlers = new HashMap<>();
public static final EventBus BUS = new AsyncEventBus(Executors.newSingleThreadExecutor());
public static void register(EventHandler handler) {
if(handlers.containsKey(handler.getId())) {
LOG.warn("EventHandler with ID {} is already registered", handler.getId());
} else {
BUS.register(handler);
handlers.put(handler.getId(), handler);
LOG.debug("EventHandler with ID {} has been added", handler.getId());
}
}
public static void unregister(String id) {
EventHandler handler = handlers.get(id);
if(handler != null) {
BUS.unregister(handler);
handlers.remove(id);
LOG.debug("EventHandler with ID {} has been removed", id);
}
}
}

View File

@ -6,35 +6,132 @@ import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
public class EventHandler {
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private List<Predicate<Event>> predicates = new ArrayList<>();
private List<Consumer<Event>> actions;
import com.google.common.eventbus.Subscribe;
import ctbrec.event.Event.Type;
import ctbrec.event.EventHandlerConfiguration.ActionConfiguration;
import ctbrec.event.EventHandlerConfiguration.PredicateConfiguration;
public class EventHandler {
private static final transient Logger LOG = LoggerFactory.getLogger(EventHandler.class);
private List<EventPredicate> predicates = new ArrayList<>();
private List<Action> actions;
private Type event;
private String id;
public EventHandler(EventHandlerConfiguration config) {
id = config.getId();
event = config.getEvent();
actions = createActions(config);
predicates = createPredicates(config);
predicates.add(new EventTypePredicate(event));
}
public String getId() {
return id;
}
@SafeVarargs
public EventHandler(Consumer<Event> action, Predicate<Event>... predicates) {
public EventHandler(Action action, EventPredicate... predicates) {
this(Collections.singletonList(action), predicates);
}
@SafeVarargs
public EventHandler(List<Consumer<Event>> actions, Predicate<Event>... predicates) {
public EventHandler(List<Action> actions, EventPredicate... predicates) {
this.actions = actions;
for (Predicate<Event> predicate : predicates) {
for (EventPredicate predicate : predicates) {
this.predicates.add(predicate);
}
}
@Subscribe
public void reactToEvent(Event evt) {
boolean matches = true;
for (Predicate<Event> predicate : predicates) {
if(!predicate.test(evt)) {
matches = false;
try {
boolean matches = true;
for (Predicate<Event> predicate : predicates) {
if(!predicate.test(evt)) {
matches = false;
}
}
}
if(matches) {
for (Consumer<Event> action : actions) {
action.accept(evt);
if(matches) {
for (Consumer<Event> action : actions) {
action.accept(evt);
}
}
} catch(Exception e) {
LOG.error("Error while processing event", e);
}
}
private List<EventPredicate> createPredicates(EventHandlerConfiguration config) {
List<EventPredicate> predicates = new ArrayList<>(config.getPredicates().size());
for (PredicateConfiguration pc : config.getPredicates()) {
try {
@SuppressWarnings("unchecked")
Class<EventPredicate> cls = (Class<EventPredicate>) Class.forName(pc.getType());
if(cls == null) {
LOG.warn("Ignoring unknown action {}", cls);
continue;
}
EventPredicate predicate = cls.newInstance();
predicate.configure(pc);
predicates.add(predicate);
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
LOG.warn("Error while creating action {} {}", pc.getType(), pc.getConfiguration(), e);
}
}
return predicates;
}
private List<Action> createActions(EventHandlerConfiguration config) {
List<Action> actions = new ArrayList<>(config.getActions().size());
for (ActionConfiguration ac : config.getActions()) {
try {
@SuppressWarnings("unchecked")
Class<Action> cls = (Class<Action>) Class.forName(ac.getType());
if(cls == null) {
LOG.warn("Ignoring unknown action {}", cls);
continue;
}
Action action = cls.newInstance();
action.configure(ac);
actions.add(action);
} catch (Exception e) {
LOG.warn("Error while creating action {} {}", ac.getType(), ac.getConfiguration(), e);
}
}
return actions;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
EventHandler other = (EventHandler) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}

View File

@ -4,14 +4,30 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import ctbrec.Model;
public class EventHandlerConfiguration {
private String id;
private String name;
private Event.Type event;
private List<PredicateConfiguration> predicates = new ArrayList<>();
private List<ActionConfiguration> actions = new ArrayList<>();
public EventHandlerConfiguration() {
id = UUID.randomUUID().toString();
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Event.Type getEvent() {
return event;
}
@ -44,10 +60,16 @@ public class EventHandlerConfiguration {
this.actions = actions;
}
public class PredicateConfiguration {
public static class PredicateConfiguration {
private String name;
private String type;
private List<Model> models;
private Map<String, Object> configuration = new HashMap<>();
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
@ -64,9 +86,22 @@ public class EventHandlerConfiguration {
this.configuration = configuration;
}
public List<Model> getModels() {
return models;
}
public void setModels(List<Model> models) {
this.models = models;
}
@Override
public String toString() {
return name;
}
}
public class ActionConfiguration {
public static class ActionConfiguration {
private String name;
private String type;
private Map<String, Object> configuration = new HashMap<>();
@ -85,5 +120,44 @@ public class EventHandlerConfiguration {
public void setConfiguration(Map<String, Object> configuration) {
this.configuration = configuration;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
@Override
public String toString() {
return name + ", when:" + predicates + " do:" + actions + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
EventHandlerConfiguration other = (EventHandlerConfiguration) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}

View File

@ -0,0 +1,10 @@
package ctbrec.event;
import java.util.function.Predicate;
import ctbrec.event.EventHandlerConfiguration.PredicateConfiguration;
public abstract class EventPredicate implements Predicate<Event> {
public abstract void configure(PredicateConfiguration pc);
}

View File

@ -1,14 +1,16 @@
package ctbrec.event;
import java.util.function.Predicate;
import ctbrec.event.Event.Type;
import ctbrec.event.EventHandlerConfiguration.PredicateConfiguration;
public class EventTypePredicate implements Predicate<Event> {
public class EventTypePredicate extends EventPredicate {
private Type type;
private EventTypePredicate(Type type) {
public EventTypePredicate() {
}
public EventTypePredicate(Type type) {
this.type = type;
}
@ -17,7 +19,8 @@ public class EventTypePredicate implements Predicate<Event> {
return evt.getType() == type;
}
public static EventTypePredicate of(Type type) {
return new EventTypePredicate(type);
@Override
public void configure(PredicateConfiguration pc) {
type = Type.valueOf((String) pc.getConfiguration().get("type"));
}
}

View File

@ -0,0 +1,53 @@
package ctbrec.event;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.OS;
import ctbrec.event.EventHandlerConfiguration.ActionConfiguration;
import ctbrec.io.StreamRedirectThread;
public class ExecuteProgram extends Action {
private static final transient Logger LOG = LoggerFactory.getLogger(ExecuteProgram.class);
private String executable;
public ExecuteProgram() {}
public ExecuteProgram(String executable) {
this.executable = executable;
name = "execute program";
}
@Override
public void accept(Event evt) {
Runtime rt = Runtime.getRuntime();
Process process = null;
try {
String[] args = {executable}; // TODO fill args array
process = rt.exec(args, OS.getEnvironment());
// create threads, which read stdout and stderr of the player process. these are needed,
// because otherwise the internal buffer for these streams fill up and block the process
Thread std = new Thread(new StreamRedirectThread(process.getInputStream(), System.out));
std.setName("Player stdout pipe");
std.setDaemon(true);
std.start();
Thread err = new Thread(new StreamRedirectThread(process.getErrorStream(), System.err));
err.setName("Player stderr pipe");
err.setDaemon(true);
err.start();
process.waitFor();
LOG.debug("{} finished", name);
} catch (Exception e) {
LOG.error("Error while processing {}", e);
}
}
@Override
public void configure(ActionConfiguration config) {
executable = (String) config.getConfiguration().get("file");
}
}

View File

@ -1,30 +1,57 @@
package ctbrec.event;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
import ctbrec.Model;
import ctbrec.event.EventHandlerConfiguration.PredicateConfiguration;
public class ModelPredicate implements Predicate<Event> {
public class ModelPredicate extends EventPredicate {
private Model model;
private Predicate<Event> internal;
private ModelPredicate(Model model) {
this.model = model;
public ModelPredicate() {}
public ModelPredicate(Model model) {
internal = createFor(model);
}
public ModelPredicate(List<Model> models) {
configure(models);
}
private void configure(List<Model> models) {
if(models.isEmpty()) {
throw new IllegalArgumentException("List has to contain at least one model");
}
Predicate<Event> predicate = createFor(models.get(0));
for (int i = 1; i < models.size(); i++) {
predicate = predicate.or(createFor(models.get(i)));
}
internal = predicate;
}
@Override
public boolean test(Event evt) {
if(evt instanceof AbstractModelEvent) {
AbstractModelEvent modelEvent = (AbstractModelEvent) evt;
Model other = modelEvent.getModel();
return Objects.equals(model, other);
} else {
return false;
}
return internal.test(evt);
}
public static ModelPredicate of(Model model) {
return new ModelPredicate(model);
private Predicate<Event> createFor(Model model) {
return evt -> {
if(evt instanceof AbstractModelEvent) {
AbstractModelEvent modelEvent = (AbstractModelEvent) evt;
Model other = modelEvent.getModel();
return Objects.equals(model, other);
} else {
return false;
}
};
}
@Override
public void configure(PredicateConfiguration pc) {
configure(pc.getModels());
}
}

View File

@ -1,20 +1,22 @@
package ctbrec.event;
import java.util.function.Predicate;
import ctbrec.Model;
import ctbrec.Model.State;
import ctbrec.event.EventHandlerConfiguration.PredicateConfiguration;
public class ModelStatePredicate implements Predicate<Event> {
public class ModelStatePredicate extends EventPredicate {
private Model.State state;
private ModelStatePredicate(Model.State state) {
public ModelStatePredicate() {}
public ModelStatePredicate(Model.State state) {
this.state = state;
}
@Override
public boolean test(Event evt) {
if(evt instanceof AbstractModelEvent) {
if(evt instanceof ModelStateChangedEvent) {
ModelStateChangedEvent modelEvent = (ModelStateChangedEvent) evt;
Model.State newState = modelEvent.getNewState();
return newState == state;
@ -23,7 +25,8 @@ public class ModelStatePredicate implements Predicate<Event> {
}
}
public static ModelStatePredicate of(Model.State state) {
return new ModelStatePredicate(state);
@Override
public void configure(PredicateConfiguration pc) {
state = State.valueOf((String) pc.getConfiguration().get("state"));
}
}

View File

@ -47,6 +47,10 @@ public class RecordingStateChangedEvent extends AbstractModelEvent {
};
}
public State getState() {
return newState;
}
@Override
public String toString() {
return "RecordingStateChanged[" + newState + "," + model.getDisplayName() + "," + path + "]";

View File

@ -0,0 +1,31 @@
package ctbrec.event;
import ctbrec.Recording;
import ctbrec.event.EventHandlerConfiguration.PredicateConfiguration;
public class RecordingStatePredicate extends EventPredicate {
private Recording.State state;
public RecordingStatePredicate() {}
public RecordingStatePredicate(Recording.State state) {
this.state = state;
}
@Override
public boolean test(Event evt) {
if(evt instanceof RecordingStateChangedEvent) {
RecordingStateChangedEvent event = (RecordingStateChangedEvent) evt;
Recording.State newState = event.getState();
return newState == state;
} else {
return false;
}
}
@Override
public void configure(PredicateConfiguration pc) {
state = Recording.State.valueOf((String) pc.getConfiguration().get("state"));
}
}