forked from j62/ctbrec
1
0
Fork 0

Add Event and EventReaction classes

This commit is contained in:
0xboobface 2018-12-06 17:39:33 +01:00
parent b50df194a0
commit 2dc5fd4581
34 changed files with 440 additions and 162 deletions

View File

@ -1,8 +1,8 @@
package ctbrec.ui; package ctbrec.ui;
import static ctbrec.EventBusHolder.*;
import static ctbrec.EventBusHolder.EVENT_TYPE.*; import static ctbrec.Model.State.*;
import static ctbrec.Model.STATUS.*; import static ctbrec.event.Event.Type.*;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
@ -14,7 +14,6 @@ import java.lang.reflect.Type;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -26,11 +25,14 @@ import com.squareup.moshi.Moshi;
import com.squareup.moshi.Types; import com.squareup.moshi.Types;
import ctbrec.Config; import ctbrec.Config;
import ctbrec.EventBusHolder;
import ctbrec.Model; import ctbrec.Model;
import ctbrec.OS; import ctbrec.OS;
import ctbrec.StringUtil; import ctbrec.StringUtil;
import ctbrec.Version; import ctbrec.Version;
import ctbrec.event.Event;
import ctbrec.event.EventBusHolder;
import ctbrec.event.LogReaction;
import ctbrec.event.ModelStateChangedEvent;
import ctbrec.io.HttpClient; import ctbrec.io.HttpClient;
import ctbrec.recorder.LocalRecorder; import ctbrec.recorder.LocalRecorder;
import ctbrec.recorder.OnlineMonitor; import ctbrec.recorder.OnlineMonitor;
@ -42,6 +44,7 @@ import ctbrec.sites.cam4.Cam4;
import ctbrec.sites.camsoda.Camsoda; import ctbrec.sites.camsoda.Camsoda;
import ctbrec.sites.chaturbate.Chaturbate; import ctbrec.sites.chaturbate.Chaturbate;
import ctbrec.sites.mfc.MyFreeCams; import ctbrec.sites.mfc.MyFreeCams;
import ctbrec.ui.settings.SettingsTab;
import javafx.application.Application; import javafx.application.Application;
import javafx.application.HostServices; import javafx.application.HostServices;
import javafx.application.Platform; import javafx.application.Platform;
@ -143,7 +146,7 @@ public class CamrecApplication extends Application {
loadStyleSheet(primaryStage, "color.css"); loadStyleSheet(primaryStage, "color.css");
} }
loadStyleSheet(primaryStage, "style.css"); loadStyleSheet(primaryStage, "style.css");
primaryStage.getScene().getStylesheets().add("/ctbrec/ui/ColorSettingsPane.css"); primaryStage.getScene().getStylesheets().add("/ctbrec/ui/settings/ColorSettingsPane.css");
primaryStage.getScene().getStylesheets().add("/ctbrec/ui/ThumbCell.css"); primaryStage.getScene().getStylesheets().add("/ctbrec/ui/ThumbCell.css");
primaryStage.getScene().getStylesheets().add("/ctbrec/ui/controls/SearchBox.css"); primaryStage.getScene().getStylesheets().add("/ctbrec/ui/controls/SearchBox.css");
primaryStage.getScene().getStylesheets().add("/ctbrec/ui/controls/Popover.css"); primaryStage.getScene().getStylesheets().add("/ctbrec/ui/controls/Popover.css");
@ -212,40 +215,43 @@ public class CamrecApplication extends Application {
} }
private void registerAlertSystem() { private void registerAlertSystem() {
new Thread(() -> { // try {
// try { // // don't register before 1 minute has passed, because directly after
// // don't register before 1 minute has passed, because directly after // // the start of ctbrec, an event for every online model would be fired,
// // the start of ctbrec, an event for every online model would be fired, // // which is annoying as f
// // which is annoying as f // Thread.sleep(TimeUnit.MINUTES.toMillis(1));
// Thread.sleep(TimeUnit.MINUTES.toMillis(1)); // } catch (InterruptedException e) {
// } catch (InterruptedException e) { // e.printStackTrace();
// e.printStackTrace(); // }
// } EventBusHolder.BUS.register(new Object() {
LOG.debug("Alert System registered"); @Subscribe
Platform.runLater(() -> { public void modelEvent(Event e) {
EventBusHolder.BUS.register(new Object() { try {
@Subscribe if (e.getType() == MODEL_STATUS_CHANGED) {
public void modelEvent(Map<String, Object> e) { ModelStateChangedEvent evt = (ModelStateChangedEvent) e;
try { Model model = evt.getModel();
if (Objects.equals(e.get(EVENT), MODEL_STATUS_CHANGED)) { if (evt.getNewState() == ONLINE) {
LOG.debug("Alert: {}", e); String header = "Model Online";
Model.STATUS status = (Model.STATUS) e.get(STATUS); String msg = model.getDisplayName() + " is now online";
Model model = (Model) e.get(MODEL); OS.notification(primaryStage.getTitle(), header, msg);
if (status == ONLINE) {
Platform.runLater(() -> {
String header = "Model Online";
String msg = model.getDisplayName() + " is now online";
OS.notification(primaryStage.getTitle(), header, msg);
});
}
}
} catch (Exception e1) {
e1.printStackTrace();
} }
} }
}); } catch (Exception e1) {
}); LOG.error("Couldn't show notification", e1);
}).start(); }
}
});
EventBusHolder.BUS.register(new Object() {
LogReaction reaction = new LogReaction();
@Subscribe
public void modelEvent(Event e) {
reaction.reactToEvent(e);
}
});
LOG.debug("Alert System registered");
} }
private void writeColorSchemeStyleSheet(Stage primaryStage) { private void writeColorSchemeStyleSheet(Stage primaryStage) {

View File

@ -110,7 +110,7 @@ public class JavaFxModel implements Model {
} }
@Override @Override
public STATUS getOnlineState(boolean failFast) throws IOException, ExecutionException { public State getOnlineState(boolean failFast) throws IOException, ExecutionException {
return delegate.getOnlineState(failFast); return delegate.getOnlineState(failFast);
} }

View File

@ -43,7 +43,7 @@ public class JavaFxRecording extends Recording {
} }
@Override @Override
public STATUS getStatus() { public State getStatus() {
return delegate.getStatus(); return delegate.getStatus();
} }
@ -52,7 +52,7 @@ public class JavaFxRecording extends Recording {
} }
@Override @Override
public void setStatus(STATUS status) { public void setStatus(State status) {
delegate.setStatus(status); delegate.setStatus(status);
switch(status) { switch(status) {
case RECORDING: case RECORDING:
@ -121,7 +121,7 @@ public class JavaFxRecording extends Recording {
public void update(Recording updated) { public void update(Recording updated) {
if(!Config.getInstance().getSettings().localRecording) { if(!Config.getInstance().getSettings().localRecording) {
if(getStatus() == STATUS.DOWNLOADING && updated.getStatus() != STATUS.DOWNLOADING) { if(getStatus() == State.DOWNLOADING && updated.getStatus() != State.DOWNLOADING) {
// ignore, because the the status coming from the server is FINISHED and we are // ignore, because the the status coming from the server is FINISHED and we are
// overriding it with DOWNLOADING // overriding it with DOWNLOADING
return; return;

View File

@ -369,7 +369,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
.map(m -> new JavaFxModel(m)) .map(m -> new JavaFxModel(m))
.peek(fxm -> { .peek(fxm -> {
for (Recording recording : recordings) { for (Recording recording : recordings) {
if(recording.getStatus() == Recording.STATUS.RECORDING && if(recording.getStatus() == Recording.State.RECORDING &&
recording.getModelName().equals(fxm.getName())) recording.getModelName().equals(fxm.getName()))
{ {
fxm.getRecordingProperty().set(true); fxm.getRecordingProperty().set(true);

View File

@ -1,5 +1,6 @@
package ctbrec.ui; package ctbrec.ui;
import static ctbrec.Recording.State.*;
import static javafx.scene.control.ButtonType.*; import static javafx.scene.control.ButtonType.*;
import java.io.File; import java.io.File;
@ -34,7 +35,7 @@ import com.iheartradio.m3u8.PlaylistException;
import ctbrec.Config; import ctbrec.Config;
import ctbrec.Recording; import ctbrec.Recording;
import ctbrec.Recording.STATUS; import ctbrec.Recording.State;
import ctbrec.StringUtil; import ctbrec.StringUtil;
import ctbrec.recorder.Recorder; import ctbrec.recorder.Recorder;
import ctbrec.recorder.download.MergedHlsDownload; import ctbrec.recorder.download.MergedHlsDownload;
@ -172,7 +173,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
if(Objects.equals(System.getenv("CTBREC_DEV"), "1")) { if(Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
int row = this.getTableRow().getIndex(); int row = this.getTableRow().getIndex();
JavaFxRecording rec = tableViewProperty().get().getItems().get(row); JavaFxRecording rec = tableViewProperty().get().getItems().get(row);
if(!rec.valueChanged() && rec.getStatus() == STATUS.RECORDING) { if(!rec.valueChanged() && rec.getStatus() == State.RECORDING) {
setStyle("-fx-alignment: CENTER-RIGHT; -fx-background-color: red"); setStyle("-fx-alignment: CENTER-RIGHT; -fx-background-color: red");
} }
} }
@ -212,11 +213,11 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
List<JavaFxRecording> recordings = table.getSelectionModel().getSelectedItems(); List<JavaFxRecording> recordings = table.getSelectionModel().getSelectedItems();
if (recordings != null && !recordings.isEmpty()) { if (recordings != null && !recordings.isEmpty()) {
if (event.getCode() == KeyCode.DELETE) { if (event.getCode() == KeyCode.DELETE) {
if(recordings.size() > 1 || recordings.get(0).getStatus() == STATUS.FINISHED) { if(recordings.size() > 1 || recordings.get(0).getStatus() == State.FINISHED) {
delete(recordings); delete(recordings);
} }
} else if (event.getCode() == KeyCode.ENTER) { } else if (event.getCode() == KeyCode.ENTER) {
if(recordings.get(0).getStatus() == STATUS.FINISHED) { if(recordings.get(0).getStatus() == State.FINISHED) {
play(recordings.get(0)); play(recordings.get(0));
} }
} }
@ -376,7 +377,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
openInPlayer.setOnAction((e) -> { openInPlayer.setOnAction((e) -> {
play(recordings.get(0)); play(recordings.get(0));
}); });
if(recordings.get(0).getStatus() == STATUS.FINISHED || Config.getInstance().getSettings().localRecording) { if(recordings.get(0).getStatus() == State.FINISHED || Config.getInstance().getSettings().localRecording) {
contextMenu.getItems().add(openInPlayer); contextMenu.getItems().add(openInPlayer);
} }
@ -398,7 +399,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
deleteRecording.setOnAction((e) -> { deleteRecording.setOnAction((e) -> {
delete(recordings); delete(recordings);
}); });
if(recordings.get(0).getStatus() == STATUS.FINISHED || recordings.size() > 1) { if(recordings.get(0).getStatus() == State.FINISHED || recordings.size() > 1) {
contextMenu.getItems().add(deleteRecording); contextMenu.getItems().add(deleteRecording);
} }
@ -424,7 +425,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
LOG.error("Error while downloading recording", e1); LOG.error("Error while downloading recording", e1);
} }
}); });
if (!Config.getInstance().getSettings().localRecording && recordings.get(0).getStatus() == STATUS.FINISHED) { if (!Config.getInstance().getSettings().localRecording && recordings.get(0).getStatus() == State.FINISHED) {
contextMenu.getItems().add(downloadRecording); contextMenu.getItems().add(downloadRecording);
} }
@ -463,11 +464,11 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
download.start(url.toString(), target, (progress) -> { download.start(url.toString(), target, (progress) -> {
Platform.runLater(() -> { Platform.runLater(() -> {
if (progress == 100) { if (progress == 100) {
recording.setStatus(STATUS.FINISHED); recording.setStatus(FINISHED);
recording.setProgress(-1); recording.setProgress(-1);
LOG.debug("Download finished for recording {}", recording.getPath()); LOG.debug("Download finished for recording {}", recording.getPath());
} else { } else {
recording.setStatus(STATUS.DOWNLOADING); recording.setStatus(DOWNLOADING);
recording.setProgress(progress); recording.setProgress(progress);
} }
}); });
@ -482,7 +483,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
Platform.runLater(new Runnable() { Platform.runLater(new Runnable() {
@Override @Override
public void run() { public void run() {
recording.setStatus(STATUS.FINISHED); recording.setStatus(FINISHED);
recording.setProgress(-1); recording.setProgress(-1);
} }
}); });
@ -493,7 +494,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
t.setName("Download Thread " + recording.getPath()); t.setName("Download Thread " + recording.getPath());
t.start(); t.start();
recording.setStatus(STATUS.DOWNLOADING); recording.setStatus(State.DOWNLOADING);
recording.setProgress(0); recording.setProgress(0);
} }
} }
@ -563,7 +564,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
List<Recording> deleted = new ArrayList<>(); List<Recording> deleted = new ArrayList<>();
for (Iterator<JavaFxRecording> iterator = recordings.iterator(); iterator.hasNext();) { for (Iterator<JavaFxRecording> iterator = recordings.iterator(); iterator.hasNext();) {
JavaFxRecording r = iterator.next(); JavaFxRecording r = iterator.next();
if(r.getStatus() != STATUS.FINISHED) { if(r.getStatus() != FINISHED) {
continue; continue;
} }
try { try {

View File

@ -25,8 +25,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import ctbrec.Config; import ctbrec.Config;
import ctbrec.EventBusHolder;
import ctbrec.Model; import ctbrec.Model;
import ctbrec.event.EventBusHolder;
import ctbrec.recorder.Recorder; import ctbrec.recorder.Recorder;
import ctbrec.sites.Site; import ctbrec.sites.Site;
import ctbrec.sites.mfc.MyFreeCamsClient; import ctbrec.sites.mfc.MyFreeCamsClient;

View File

@ -9,7 +9,7 @@ import org.slf4j.LoggerFactory;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
import ctbrec.EventBusHolder; import ctbrec.event.EventBusHolder;
import ctbrec.sites.Site; import ctbrec.sites.Site;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.concurrent.Task; import javafx.concurrent.Task;

View File

@ -0,0 +1,30 @@
package ctbrec.ui.settings;
import ctbrec.event.Event.Type;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TitledPane;
import javafx.scene.layout.GridPane;
public class ActionSettingsPanel extends TitledPane {
public ActionSettingsPanel(SettingsTab settingsTab) {
setText("Actions");
setExpanded(true);
setCollapsible(false);
createGui();
}
private void createGui() {
GridPane mainLayout = SettingsTab.createGridLayout();
setContent(mainLayout);
int row = 0;
for (Type type : Type.values()) {
Label l = new Label(type.name());
mainLayout.add(l, 0, row);
Button b = new Button("Configure");
mainLayout.add(b, 1, row++);
}
}
}

View File

@ -1,6 +1,6 @@
package ctbrec.ui.sites.bonga; package ctbrec.ui.sites.bonga;
import static ctbrec.Model.STATUS.*; import static ctbrec.Model.State.*;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;

View File

@ -1,6 +1,6 @@
package ctbrec.ui.sites.camsoda; package ctbrec.ui.sites.camsoda;
import static ctbrec.Model.STATUS.*; import static ctbrec.Model.State.*;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;

View File

@ -21,7 +21,7 @@ public abstract class AbstractModel implements Model {
private int streamUrlIndex = -1; private int streamUrlIndex = -1;
private boolean suspended = false; private boolean suspended = false;
protected Site site; protected Site site;
protected STATUS onlineState = STATUS.UNKNOWN; protected State onlineState = State.UNKNOWN;
@Override @Override
public boolean isOnline() throws IOException, ExecutionException, InterruptedException { public boolean isOnline() throws IOException, ExecutionException, InterruptedException {
@ -123,11 +123,11 @@ public abstract class AbstractModel implements Model {
} }
@Override @Override
public STATUS getOnlineState(boolean failFast) throws IOException, ExecutionException { public State getOnlineState(boolean failFast) throws IOException, ExecutionException {
return onlineState; return onlineState;
} }
public void setOnlineState(STATUS status) { public void setOnlineState(State status) {
this.onlineState = status; this.onlineState = status;
} }

View File

@ -14,7 +14,7 @@ import ctbrec.sites.Site;
public interface Model { public interface Model {
public static enum STATUS { public static enum State {
ONLINE("online"), ONLINE("online"),
OFFLINE("offline"), OFFLINE("offline"),
AWAY("away"), AWAY("away"),
@ -23,7 +23,7 @@ public interface Model {
UNKNOWN("unknown"); UNKNOWN("unknown");
String display; String display;
STATUS(String display) { State(String display) {
this.display = display; this.display = display;
} }
@ -65,7 +65,7 @@ public interface Model {
public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException; public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException;
public STATUS getOnlineState(boolean failFast) throws IOException, ExecutionException; public State getOnlineState(boolean failFast) throws IOException, ExecutionException;
public List<StreamSource> getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException; public List<StreamSource> getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException;

View File

@ -10,11 +10,11 @@ public class Recording {
private Instant startDate; private Instant startDate;
private String path; private String path;
private boolean hasPlaylist; private boolean hasPlaylist;
private STATUS status = STATUS.UNKNOWN; private State status = State.UNKNOWN;
private int progress = -1; private int progress = -1;
private long sizeInByte; private long sizeInByte;
public static enum STATUS { public static enum State {
RECORDING, RECORDING,
GENERATING_PLAYLIST, GENERATING_PLAYLIST,
STOPPED, STOPPED,
@ -50,11 +50,11 @@ public class Recording {
this.startDate = startDate; this.startDate = startDate;
} }
public STATUS getStatus() { public State getStatus() {
return status; return status;
} }
public void setStatus(STATUS status) { public void setStatus(State status) {
this.status = status; this.status = status;
} }

View File

@ -0,0 +1,16 @@
package ctbrec.event;
import ctbrec.Model;
public abstract class AbstractModelEvent extends Event {
protected Model model;
public Model getModel() {
return model;
}
public void setModel(Model model) {
this.model = model;
}
}

View File

@ -0,0 +1,28 @@
package ctbrec.event;
public abstract class Event {
public static enum Type {
/**
* This event is fired every time the OnlineMonitor sees a model online
* It is also fired, if the model was online before. You can see it as a "still online ping".
*/
MODEL_ONLINE,
/**
* This event is fired whenever the model's online state (Model.STATUS) changes.
*/
MODEL_STATUS_CHANGED,
/**
* This event is fired whenever the state of a recording changes.
*/
RECORDING_STATUS_CHANGED
}
public abstract Type getType();
public abstract String getName();
public abstract String getDescription();
public abstract String[] getExecutionParams();
}

View File

@ -1,4 +1,4 @@
package ctbrec; package ctbrec.event;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -12,24 +12,5 @@ public class EventBusHolder {
public static final String STATUS = "status"; public static final String STATUS = "status";
public static final String MODEL = "model"; public static final String MODEL = "model";
public static enum EVENT_TYPE {
/**
* This event is fired every time the OnlineMonitor sees a model online
* It is also fired, if the model was online before. You can see it as a "still online ping".
*/
MODEL_ONLINE,
/**
* This event is fired whenever the model's online state (Model.STATUS) changes.
*/
MODEL_STATUS_CHANGED,
/**
* This event is fired whenever the state of a recording changes.
*/
RECORDING_STATUS_CHANGED
}
public static final EventBus BUS = new AsyncEventBus(Executors.newSingleThreadExecutor()); public static final EventBus BUS = new AsyncEventBus(Executors.newSingleThreadExecutor());
} }

View File

@ -0,0 +1,32 @@
package ctbrec.event;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
public class EventReaction {
private List<Predicate<Event>> predicates = new ArrayList<>();
private Consumer<Event> action;
@SafeVarargs
public EventReaction(Consumer<Event> action, Predicate<Event>... predicates) {
this.action = action;
for (Predicate<Event> predicate : predicates) {
this.predicates.add(predicate);
}
}
public void reactToEvent(Event evt) {
boolean matches = true;
for (Predicate<Event> predicate : predicates) {
if(!predicate.test(evt)) {
matches = false;
}
}
if(matches) {
action.accept(evt);
}
}
}

View File

@ -0,0 +1,15 @@
package ctbrec.event;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogReaction extends EventReaction {
private static final transient Logger LOG = LoggerFactory.getLogger(LogReaction.class);
public LogReaction() {
super(evt -> {
LOG.debug("LogReaction: {}", evt);
}, TypePredicate.of(Event.Type.RECORDING_STATUS_CHANGED));
}
}

View File

@ -0,0 +1,36 @@
package ctbrec.event;
import ctbrec.Model;
public class ModelIsOnlineEvent extends AbstractModelEvent {
public ModelIsOnlineEvent(Model model) {
super.model = model;
}
@Override
public Type getType() {
return Event.Type.MODEL_ONLINE;
}
@Override
public String getName() {
return "Model is online";
}
@Override
public String getDescription() {
return "Repeatedly fired when a model is online";
}
@Override
public String[] getExecutionParams() {
return new String[] {
getType().toString(),
model.getDisplayName(),
model.getUrl(),
model.getSite().getName()
};
}
}

View File

@ -0,0 +1,59 @@
package ctbrec.event;
import ctbrec.Model;
import ctbrec.Model.State;
public class ModelStateChangedEvent extends AbstractModelEvent {
private State oldState;
private State newState;
public ModelStateChangedEvent(Model model, Model.State oldState, Model.State newState) {
super.model = model;
this.oldState = oldState;
this.newState = newState;
}
@Override
public Type getType() {
return Event.Type.MODEL_STATUS_CHANGED;
}
@Override
public String getName() {
return "Model state changed";
}
@Override
public String getDescription() {
return "Fired when a model state changed. E.g. from OFFLINE to ONLINE";
}
@Override
public String[] getExecutionParams() {
return new String[] {
getType().toString(),
model.getDisplayName(),
model.getUrl(),
model.getSite().getName(),
oldState.toString(),
newState.toString()
};
}
public State getOldState() {
return oldState;
}
public void setOldState(State oldState) {
this.oldState = oldState;
}
public State getNewState() {
return newState;
}
public void setNewState(State newState) {
this.newState = newState;
}
}

View File

@ -0,0 +1,55 @@
package ctbrec.event;
import java.io.File;
import java.time.Instant;
import ctbrec.Model;
import ctbrec.Recording.State;
public class RecordingStateChangedEvent extends AbstractModelEvent {
private File path;
private State newState;
private Instant startTime;
public RecordingStateChangedEvent(File recording, State newState, Model model, Instant startTime) {
super.model = model;
this.path = recording;
this.newState = newState;
this.startTime = startTime;
}
@Override
public Type getType() {
return Event.Type.RECORDING_STATUS_CHANGED;
}
@Override
public String getName() {
return "Recording state changed";
}
@Override
public String getDescription() {
return "Fired when a recording state changed. E.g. from RECORDING to STOPPED";
}
@Override
public String[] getExecutionParams() {
return new String[] {
getType().toString(),
path.getAbsolutePath(),
newState.toString(),
model.getDisplayName(),
model.getSite().getName(),
model.getUrl(),
Long.toString(startTime.getEpochSecond())
};
}
@Override
public String toString() {
return "RecordingStateChanged[" + newState + "," + model.getDisplayName() + "," + path + "]";
}
}

View File

@ -0,0 +1,23 @@
package ctbrec.event;
import java.util.function.Predicate;
import ctbrec.event.Event.Type;
public class TypePredicate implements Predicate<Event> {
private Type type;
private TypePredicate(Type type) {
this.type = type;
}
@Override
public boolean test(Event evt) {
return evt.getType() == type;
}
public static TypePredicate of(Type type) {
return new TypePredicate(type);
}
}

View File

@ -26,7 +26,7 @@ public class StreamRedirectThread implements Runnable {
while(in != null && (length = in.read(buffer)) >= 0) { while(in != null && (length = in.read(buffer)) >= 0) {
out.write(buffer, 0, length); out.write(buffer, 0, length);
} }
LOG.debug("Stream redirect thread ended"); LOG.trace("Stream redirect thread ended");
} catch(Exception e) { } catch(Exception e) {
LOG.error("Couldn't redirect stream: {}", e.getLocalizedMessage()); LOG.error("Couldn't redirect stream: {}", e.getLocalizedMessage());
} }

View File

@ -1,8 +1,7 @@
package ctbrec.recorder; package ctbrec.recorder;
import static ctbrec.EventBusHolder.*; import static ctbrec.Recording.State.*;
import static ctbrec.EventBusHolder.EVENT_TYPE.*; import static ctbrec.event.Event.Type.*;
import static ctbrec.Recording.STATUS.*;
import java.io.File; import java.io.File;
import java.io.FilenameFilter; import java.io.FilenameFilter;
@ -24,7 +23,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@ -38,11 +36,14 @@ import com.iheartradio.m3u8.ParseException;
import com.iheartradio.m3u8.PlaylistException; import com.iheartradio.m3u8.PlaylistException;
import ctbrec.Config; import ctbrec.Config;
import ctbrec.EventBusHolder;
import ctbrec.Model; import ctbrec.Model;
import ctbrec.OS; import ctbrec.OS;
import ctbrec.Recording; import ctbrec.Recording;
import ctbrec.Recording.STATUS; import ctbrec.Recording.State;
import ctbrec.event.Event;
import ctbrec.event.EventBusHolder;
import ctbrec.event.ModelIsOnlineEvent;
import ctbrec.event.RecordingStateChangedEvent;
import ctbrec.io.HttpClient; import ctbrec.io.HttpClient;
import ctbrec.io.StreamRedirectThread; import ctbrec.io.StreamRedirectThread;
import ctbrec.recorder.PlaylistGenerator.InvalidPlaylistException; import ctbrec.recorder.PlaylistGenerator.InvalidPlaylistException;
@ -97,10 +98,11 @@ public class LocalRecorder implements Recorder {
private void registerEventBusListener() { private void registerEventBusListener() {
EventBusHolder.BUS.register(new Object() { EventBusHolder.BUS.register(new Object() {
@Subscribe @Subscribe
public void modelEvent(Map<String, Object> e) { public void modelEvent(Event e) {
try { try {
if (Objects.equals(e.get(EVENT), MODEL_ONLINE)) { if (e.getType() == MODEL_ONLINE) {
Model model = (Model) e.get(MODEL); ModelIsOnlineEvent evt = (ModelIsOnlineEvent) e;
Model model = evt.getModel();
if(!isSuspended(model) && !recordingProcesses.containsKey(model)) { if(!isSuspended(model) && !recordingProcesses.containsKey(model)) {
startRecordingProcess(model); startRecordingProcess(model);
} }
@ -198,7 +200,6 @@ public class LocalRecorder implements Recorder {
} }
} }
}.start(); }.start();
fireRecordingStateChanged(model, RECORDING);
} }
private void stopRecordingProcess(Model model) { private void stopRecordingProcess(Model model) {
@ -208,7 +209,7 @@ public class LocalRecorder implements Recorder {
if(!Config.isServerMode()) { if(!Config.isServerMode()) {
postprocess(download); postprocess(download);
} }
fireRecordingStateChanged(model, FINISHED); fireRecordingStateChanged(download.getTarget(), FINISHED, model, download.getStartTime());
} }
private void postprocess(Download download) { private void postprocess(Download download) {
@ -388,7 +389,7 @@ public class LocalRecorder implements Recorder {
} else { } else {
postprocess(d); postprocess(d);
} }
fireRecordingStateChanged(m, FINISHED); // TODO fire all the events fireRecordingStateChanged(d.getTarget(), FINISHED, m, d.getStartTime()); // TODO fire all the events
} }
} }
for (Model m : restart) { for (Model m : restart) {
@ -439,13 +440,9 @@ public class LocalRecorder implements Recorder {
} }
} }
private void fireRecordingStateChanged(Model model, Recording.STATUS status) { private void fireRecordingStateChanged(File path, Recording.State newState, Model model, Instant startTime) {
Map<String, Object> evt = new HashMap<>(); RecordingStateChangedEvent evt = new RecordingStateChangedEvent(path, newState, model, startTime);
evt.put(EVENT, RECORDING_STATUS_CHANGED);
evt.put(STATUS, status);
evt.put(MODEL, model);
EventBusHolder.BUS.post(evt); EventBusHolder.BUS.post(evt);
LOG.debug("Event fired {}", evt);
} }
private class PostProcessingTrigger extends Thread { private class PostProcessingTrigger extends Thread {
@ -532,7 +529,7 @@ public class LocalRecorder implements Recorder {
return recordings; return recordings;
} }
private STATUS getStatus(Recording recording) { private State getStatus(Recording recording) {
File absolutePath = new File(Config.getInstance().getSettings().recordingsDir, recording.getPath()); File absolutePath = new File(Config.getInstance().getSettings().recordingsDir, recording.getPath());
PlaylistGenerator playlistGenerator = playlistGenerators.get(absolutePath); PlaylistGenerator playlistGenerator = playlistGenerators.get(absolutePath);

View File

@ -1,8 +1,6 @@
package ctbrec.recorder; package ctbrec.recorder;
import static ctbrec.EventBusHolder.*; import static ctbrec.Model.State.*;
import static ctbrec.EventBusHolder.EVENT_TYPE.*;
import static ctbrec.Model.STATUS.*;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
@ -18,9 +16,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import ctbrec.Config; import ctbrec.Config;
import ctbrec.EventBusHolder;
import ctbrec.Model; import ctbrec.Model;
import ctbrec.Model.STATUS; import ctbrec.event.EventBusHolder;
import ctbrec.event.ModelIsOnlineEvent;
import ctbrec.event.ModelStateChangedEvent;
import ctbrec.io.HttpException; import ctbrec.io.HttpException;
public class OnlineMonitor extends Thread { public class OnlineMonitor extends Thread {
@ -30,7 +29,7 @@ public class OnlineMonitor extends Thread {
private volatile boolean running = false; private volatile boolean running = false;
private Recorder recorder; private Recorder recorder;
private Map<Model, STATUS> states = new HashMap<>(); private Map<Model, Model.State> states = new HashMap<>();
public OnlineMonitor(Recorder recorder) { public OnlineMonitor(Recorder recorder) {
this.recorder = recorder; this.recorder = recorder;
@ -57,13 +56,13 @@ public class OnlineMonitor extends Thread {
for (Model model : models) { for (Model model : models) {
try { try {
if(model.isOnline(IGNORE_CACHE)) { if(model.isOnline(IGNORE_CACHE)) {
fireModelOnline(model); EventBusHolder.BUS.post(new ModelIsOnlineEvent(model));
} }
STATUS state = model.getOnlineState(false); Model.State state = model.getOnlineState(false);
STATUS oldState = states.getOrDefault(model, UNKNOWN); Model.State oldState = states.getOrDefault(model, UNKNOWN);
states.put(model, state); states.put(model, state);
if(state != oldState) { if(state != oldState) {
fireModelOnlineStateChanged(model, oldState, state); EventBusHolder.BUS.post(new ModelStateChangedEvent(model, oldState, state));
} }
} catch (HttpException e) { } catch (HttpException e) {
LOG.error("Couldn't check if model {} is online. HTTP Response: {} - {}", LOG.error("Couldn't check if model {} is online. HTTP Response: {} - {}",
@ -98,22 +97,6 @@ public class OnlineMonitor extends Thread {
LOG.debug(getName() + " terminated"); LOG.debug(getName() + " terminated");
} }
private void fireModelOnline(Model model) {
Map<String, Object> evt = new HashMap<>();
evt.put(EVENT, MODEL_ONLINE);
evt.put(MODEL, model);
EventBusHolder.BUS.post(evt);
}
private void fireModelOnlineStateChanged(Model model, STATUS oldStatus, STATUS newStatus) {
Map<String, Object> evt = new HashMap<>();
evt.put(EVENT, MODEL_STATUS_CHANGED);
evt.put(STATUS, newStatus);
evt.put(OLD, oldStatus);
evt.put(MODEL, model);
EventBusHolder.BUS.post(evt);
}
public void shutdown() { public void shutdown() {
running = false; running = false;
interrupt(); interrupt();

View File

@ -18,10 +18,10 @@ import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.Moshi; import com.squareup.moshi.Moshi;
import ctbrec.Config; import ctbrec.Config;
import ctbrec.EventBusHolder;
import ctbrec.Hmac; import ctbrec.Hmac;
import ctbrec.Model; import ctbrec.Model;
import ctbrec.Recording; import ctbrec.Recording;
import ctbrec.event.EventBusHolder;
import ctbrec.io.HttpClient; import ctbrec.io.HttpClient;
import ctbrec.io.HttpException; import ctbrec.io.HttpException;
import ctbrec.io.InstantJsonAdapter; import ctbrec.io.InstantJsonAdapter;

View File

@ -1,5 +1,7 @@
package ctbrec.recorder.download; package ctbrec.recorder.download;
import static ctbrec.Recording.State.*;
import java.io.EOFException; import java.io.EOFException;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@ -24,6 +26,8 @@ import com.iheartradio.m3u8.PlaylistException;
import ctbrec.Config; import ctbrec.Config;
import ctbrec.Model; import ctbrec.Model;
import ctbrec.event.EventBusHolder;
import ctbrec.event.RecordingStateChangedEvent;
import ctbrec.io.HttpClient; import ctbrec.io.HttpClient;
import ctbrec.io.HttpException; import ctbrec.io.HttpException;
import okhttp3.Request; import okhttp3.Request;
@ -54,6 +58,10 @@ public class HlsDownload extends AbstractHlsDownload {
throw new IOException(model.getName() +"'s room is not public"); throw new IOException(model.getName() +"'s room is not public");
} }
// let the world know, that we are recording now
RecordingStateChangedEvent evt = new RecordingStateChangedEvent(getTarget(), RECORDING, model, getStartTime());
EventBusHolder.BUS.post(evt);
String segments = getSegmentPlaylistUrl(model); String segments = getSegmentPlaylistUrl(model);
if(segments != null) { if(segments != null) {
if (!Files.exists(downloadDir, LinkOption.NOFOLLOW_LINKS)) { if (!Files.exists(downloadDir, LinkOption.NOFOLLOW_LINKS)) {

View File

@ -1,5 +1,6 @@
package ctbrec.recorder.download; package ctbrec.recorder.download;
import static ctbrec.Recording.State.*;
import static java.nio.file.StandardOpenOption.*; import static java.nio.file.StandardOpenOption.*;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@ -44,6 +45,8 @@ import com.iheartradio.m3u8.PlaylistException;
import ctbrec.Config; import ctbrec.Config;
import ctbrec.Hmac; import ctbrec.Hmac;
import ctbrec.Model; import ctbrec.Model;
import ctbrec.event.EventBusHolder;
import ctbrec.event.RecordingStateChangedEvent;
import ctbrec.io.HttpClient; import ctbrec.io.HttpClient;
import ctbrec.io.HttpException; import ctbrec.io.HttpException;
import ctbrec.recorder.ProgressListener; import ctbrec.recorder.ProgressListener;
@ -126,6 +129,11 @@ public class MergedHlsDownload extends AbstractHlsDownload {
splitRecStartTime = ZonedDateTime.now(); splitRecStartTime = ZonedDateTime.now();
super.model = model; super.model = model;
targetFile = Config.getInstance().getFileForRecording(model); targetFile = Config.getInstance().getFileForRecording(model);
// let the world know, that we are recording now
RecordingStateChangedEvent evt = new RecordingStateChangedEvent(getTarget(), RECORDING, model, getStartTime());
EventBusHolder.BUS.post(evt);
String segments = getSegmentPlaylistUrl(model); String segments = getSegmentPlaylistUrl(model);
mergeThread = createMergeThread(targetFile, null, true); mergeThread = createMergeThread(targetFile, null, true);
mergeThread.start(); mergeThread.start();

View File

@ -1,6 +1,6 @@
package ctbrec.sites.bonga; package ctbrec.sites.bonga;
import static ctbrec.Model.STATUS.*; import static ctbrec.Model.State.*;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -85,7 +85,7 @@ public class BongaCamsModel extends AbstractModel {
} }
@Override @Override
public STATUS getOnlineState(boolean failFast) throws IOException, ExecutionException { public State getOnlineState(boolean failFast) throws IOException, ExecutionException {
if(failFast) { if(failFast) {
return onlineState; return onlineState;
} else { } else {
@ -97,7 +97,7 @@ public class BongaCamsModel extends AbstractModel {
} }
@Override @Override
public void setOnlineState(STATUS onlineState) { public void setOnlineState(State onlineState) {
this.onlineState = onlineState; this.onlineState = onlineState;
} }

View File

@ -1,6 +1,6 @@
package ctbrec.sites.cam4; package ctbrec.sites.cam4;
import static ctbrec.Model.STATUS.*; import static ctbrec.Model.State.*;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -109,7 +109,7 @@ public class Cam4Model extends AbstractModel {
} }
@Override @Override
public STATUS getOnlineState(boolean failFast) throws IOException, ExecutionException { public State getOnlineState(boolean failFast) throws IOException, ExecutionException {
if(failFast) { if(failFast) {
return onlineState; return onlineState;
} else { } else {

View File

@ -1,6 +1,6 @@
package ctbrec.sites.camsoda; package ctbrec.sites.camsoda;
import static ctbrec.Model.STATUS.*; import static ctbrec.Model.State.*;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -102,7 +102,7 @@ public class CamsodaModel extends AbstractModel {
} }
@Override @Override
public STATUS getOnlineState(boolean failFast) throws IOException, ExecutionException { public State getOnlineState(boolean failFast) throws IOException, ExecutionException {
if(failFast) { if(failFast) {
return onlineState; return onlineState;
} else { } else {

View File

@ -1,6 +1,6 @@
package ctbrec.sites.chaturbate; package ctbrec.sites.chaturbate;
import static ctbrec.Model.STATUS.*; import static ctbrec.Model.State.*;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@ -76,12 +76,12 @@ public class ChaturbateModel extends AbstractModel {
getChaturbate().streamInfoCache.invalidate(getName()); getChaturbate().streamInfoCache.invalidate(getName());
} }
public STATUS getOnlineState() throws IOException, ExecutionException { public State getOnlineState() throws IOException, ExecutionException {
return getOnlineState(false); return getOnlineState(false);
} }
@Override @Override
public STATUS getOnlineState(boolean failFast) throws IOException, ExecutionException { public State getOnlineState(boolean failFast) throws IOException, ExecutionException {
if(failFast) { if(failFast) {
StreamInfo info = getChaturbate().streamInfoCache.getIfPresent(getName()); StreamInfo info = getChaturbate().streamInfoCache.getIfPresent(getName());
setOnlineStateByRoomStatus(info.room_status); setOnlineStateByRoomStatus(info.room_status);
@ -109,11 +109,11 @@ public class ChaturbateModel extends AbstractModel {
onlineState = AWAY; onlineState = AWAY;
break; break;
case "group": case "group":
onlineState = STATUS.GROUP; onlineState = State.GROUP;
break; break;
default: default:
LOG.debug("Unknown show type {}", room_status); LOG.debug("Unknown show type {}", room_status);
onlineState = STATUS.UNKNOWN; onlineState = State.UNKNOWN;
} }
} }
} }

View File

@ -601,7 +601,7 @@ public class MyFreeCamsClient {
String name = json.getString("nm"); String name = json.getString("nm");
MyFreeCamsModel model = mfc.createModel(name); MyFreeCamsModel model = mfc.createModel(name);
model.setUid(json.getInt("uid")); model.setUid(json.getInt("uid"));
model.setState(State.of(json.getInt("vs"))); model.setMfcState(State.of(json.getInt("vs")));
String uid = Integer.toString(model.getUid()); String uid = Integer.toString(model.getUid());
String uidStart = uid.substring(0, 3); String uidStart = uid.substring(0, 3);
String previewUrl = "https://img.mfcimg.com/photos2/"+uidStart+'/'+uid+"/avatar.90x90.jpg"; String previewUrl = "https://img.mfcimg.com/photos2/"+uidStart+'/'+uid+"/avatar.90x90.jpg";

View File

@ -46,7 +46,7 @@ public class MyFreeCamsModel extends AbstractModel {
private String hlsUrl; private String hlsUrl;
private double camScore; private double camScore;
private int viewerCount; private int viewerCount;
private State state; private ctbrec.sites.mfc.State state;
private int resolution[] = new int[2]; private int resolution[] = new int[2];
/** /**
@ -61,7 +61,7 @@ public class MyFreeCamsModel extends AbstractModel {
@Override @Override
public boolean isOnline() throws IOException, ExecutionException, InterruptedException { public boolean isOnline() throws IOException, ExecutionException, InterruptedException {
MyFreeCamsClient.getInstance().update(this); MyFreeCamsClient.getInstance().update(this);
return state == State.ONLINE; return state == ctbrec.sites.mfc.State.ONLINE;
} }
@Override @Override
@ -70,27 +70,27 @@ public class MyFreeCamsModel extends AbstractModel {
} }
@Override @Override
public STATUS getOnlineState(boolean failFast) throws IOException, ExecutionException { public State getOnlineState(boolean failFast) throws IOException, ExecutionException {
if(state == null) { if(state == null) {
return STATUS.UNKNOWN; return State.UNKNOWN;
} }
switch(state) { switch(state) {
case ONLINE: case ONLINE:
case RECORDING: case RECORDING:
return ctbrec.Model.STATUS.ONLINE; return ctbrec.Model.State.ONLINE;
case AWAY: case AWAY:
return ctbrec.Model.STATUS.AWAY; return ctbrec.Model.State.AWAY;
case PRIVATE: case PRIVATE:
return ctbrec.Model.STATUS.PRIVATE; return ctbrec.Model.State.PRIVATE;
case GROUP_SHOW: case GROUP_SHOW:
return ctbrec.Model.STATUS.GROUP; return ctbrec.Model.State.GROUP;
case OFFLINE: case OFFLINE:
case CAMOFF: case CAMOFF:
return ctbrec.Model.STATUS.OFFLINE; return ctbrec.Model.State.OFFLINE;
default: default:
LOG.debug("State {} is not mapped", this.state); LOG.debug("State {} is not mapped", this.state);
return ctbrec.Model.STATUS.UNKNOWN; return ctbrec.Model.State.UNKNOWN;
} }
} }
@ -233,7 +233,7 @@ public class MyFreeCamsModel extends AbstractModel {
this.camScore = camScore; this.camScore = camScore;
} }
public void setState(State state) { public void setMfcState(ctbrec.sites.mfc.State state) {
this.state = state; this.state = state;
} }
@ -249,7 +249,7 @@ public class MyFreeCamsModel extends AbstractModel {
public void update(SessionState state, String streamUrl) { public void update(SessionState state, String streamUrl) {
uid = Integer.parseInt(state.getUid().toString()); uid = Integer.parseInt(state.getUid().toString());
setName(state.getNm()); setName(state.getNm());
setState(State.of(state.getVs())); setMfcState(ctbrec.sites.mfc.State.of(state.getVs()));
setStreamUrl(streamUrl); setStreamUrl(streamUrl);
Optional<Double> camScore = Optional.ofNullable(state.getM()).map(m -> m.getCamscore()); Optional<Double> camScore = Optional.ofNullable(state.getM()).map(m -> m.getCamscore());
setCamScore(camScore.orElse(0.0)); setCamScore(camScore.orElse(0.0));
@ -258,7 +258,7 @@ public class MyFreeCamsModel extends AbstractModel {
String uid = state.getUid().toString(); String uid = state.getUid().toString();
String uidStart = uid.substring(0, 3); String uidStart = uid.substring(0, 3);
String previewUrl = "https://img.mfcimg.com/photos2/"+uidStart+'/'+uid+"/avatar.300x300.jpg"; String previewUrl = "https://img.mfcimg.com/photos2/"+uidStart+'/'+uid+"/avatar.300x300.jpg";
if(MyFreeCamsModel.this.state == State.ONLINE) { if(MyFreeCamsModel.this.state == ctbrec.sites.mfc.State.ONLINE) {
try { try {
previewUrl = getLivePreviewUrl(state); previewUrl = getLivePreviewUrl(state);
} catch(Exception e) { } catch(Exception e) {