Merge branch 'dev' into pp

This commit is contained in:
0xb00bface 2020-08-21 19:15:08 +02:00
commit 4f8e7dbca2
21 changed files with 212 additions and 45 deletions

View File

@ -1,12 +1,24 @@
3.9.0 3.9.0
======================== ========================
* Added support for Manyvids Live. * Added support for Manyvids Live.
Things that work:
* Recording streams. Even more than one (this was a problem first, because
they allow only one stream per session)
* Search
Things that don't work: Things that don't work:
* login / favorites * login / favorites
* tipping
* media player isn't working because of their authetication mechanism * media player isn't working because of their authetication mechanism
* Fixed bug in recorder servlet. Actions for unpin and notes were mixed up * Fixed bug in recorder servlet. Actions for unpin and notes were mixed up
and not properly synchronized between the server and the client
* Recordings now start immediately for newly added models * Recordings now start immediately for newly added models
* Added confirmation dialog for "Pause All" and "Resume All" * Added confirmation dialog for "Pause All", "Resume All" and shutdown
* Fix: recording started event was not fired in client / server mode
* CTB Recorder now stops recording, if less than 100 MiB space is left
* New event, which is fired, if the disk is full (or less than the configured
threshold is available)
* Fixed: MFC models changing to other models (I think, I found the problem.
Can't be sure 100%)
3.8.6 3.8.6
======================== ========================

View File

@ -14,7 +14,6 @@ import java.util.ArrayList;
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.concurrent.TimeUnit;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -317,24 +316,12 @@ public class CamrecApplication extends Application {
} }
private void registerAlertSystem() { private void registerAlertSystem() {
new Thread(() -> { for (EventHandlerConfiguration eventHandlerConfig : Config.getInstance().getSettings().eventHandlers) {
try { EventHandler handler = new EventHandler(eventHandlerConfig);
// don't register before 1 minute has passed, because directly after EventBusHolder.register(handler);
// the start of ctbrec, an event for every online model would be fired, LOG.debug("Registered event handler for {} {}", eventHandlerConfig.getEvent(), eventHandlerConfig.getName());
// which is annoying as f }
Thread.sleep(TimeUnit.MINUTES.toMillis(1)); LOG.debug("Alert System registered");
for (EventHandlerConfiguration eventHandlerConfig : Config.getInstance().getSettings().eventHandlers) {
EventHandler handler = new EventHandler(eventHandlerConfig);
EventBusHolder.register(handler);
LOG.debug("Registered event handler for {} {}", eventHandlerConfig.getEvent(), eventHandlerConfig.getName());
}
LOG.debug("Alert System registered");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
LOG.info("Interrupted before alter system has been registered");
}
}).start();
} }
private void registerActiveRecordingsCounter() { private void registerActiveRecordingsCounter() {

View File

@ -22,6 +22,9 @@ public class ShowNotification extends Action {
switch(evt.getType()) { switch(evt.getType()) {
case MODEL_STATUS_CHANGED: case MODEL_STATUS_CHANGED:
ModelStateChangedEvent modelEvent = (ModelStateChangedEvent) evt; ModelStateChangedEvent modelEvent = (ModelStateChangedEvent) evt;
if (modelEvent.getOldState() == Model.State.UNKNOWN) {
return;
}
Model m = modelEvent.getModel(); Model m = modelEvent.getModel();
msg = m.getDisplayName() + " is now " + modelEvent.getNewState().toString(); msg = m.getDisplayName() + " is now " + modelEvent.getNewState().toString();
break; break;

View File

@ -23,6 +23,7 @@ import ctbrec.event.EventHandlerConfiguration;
import ctbrec.event.EventHandlerConfiguration.ActionConfiguration; import ctbrec.event.EventHandlerConfiguration.ActionConfiguration;
import ctbrec.event.EventHandlerConfiguration.PredicateConfiguration; import ctbrec.event.EventHandlerConfiguration.PredicateConfiguration;
import ctbrec.event.ExecuteProgram; import ctbrec.event.ExecuteProgram;
import ctbrec.event.MatchAllPredicate;
import ctbrec.event.ModelPredicate; import ctbrec.event.ModelPredicate;
import ctbrec.event.ModelStatePredicate; import ctbrec.event.ModelStatePredicate;
import ctbrec.event.RecordingStatePredicate; import ctbrec.event.RecordingStatePredicate;
@ -152,6 +153,11 @@ public class ActionSettingsPanel extends GridPane {
pc.getConfiguration().put("state", recordingState.getValue().name()); pc.getConfiguration().put("state", recordingState.getValue().name());
pc.setName("state = " + recordingState.getValue().toString()); pc.setName("state = " + recordingState.getValue().toString());
config.getPredicates().add(pc); config.getPredicates().add(pc);
} else if(event.getValue() == Event.Type.NO_SPACE_LEFT) {
PredicateConfiguration pc = new PredicateConfiguration();
pc.setType(MatchAllPredicate.class.getName());
pc.setName("no space left");
config.getPredicates().add(pc);
} }
if(!modelSelectionPane.isAllSelected()) { if(!modelSelectionPane.isAllSelected()) {
PredicateConfiguration pc = new PredicateConfiguration(); PredicateConfiguration pc = new PredicateConfiguration();
@ -200,7 +206,7 @@ public class ActionSettingsPanel extends GridPane {
if(event.getValue() == Event.Type.RECORDING_STATUS_CHANGED && recordingState.getValue() == null) { if(event.getValue() == Event.Type.RECORDING_STATUS_CHANGED && recordingState.getValue() == null) {
throw new IllegalStateException("Select a state"); throw new IllegalStateException("Select a state");
} }
if(modelSelectionPane.getSelectedItems().isEmpty() && !modelSelectionPane.isAllSelected()) { if(event.getValue() != Event.Type.NO_SPACE_LEFT && modelSelectionPane.getSelectedItems().isEmpty() && !modelSelectionPane.isAllSelected()) {
throw new IllegalStateException("Select one or more models or tick off \"all\""); throw new IllegalStateException("Select one or more models or tick off \"all\"");
} }
if(!(showNotification.isSelected() || playSound.isSelected() || executeProgram.isSelected())) { if(!(showNotification.isSelected() || playSound.isSelected() || executeProgram.isSelected())) {
@ -231,10 +237,23 @@ public class ActionSettingsPanel extends GridPane {
event.getItems().clear(); event.getItems().clear();
event.getItems().add(Event.Type.MODEL_STATUS_CHANGED); event.getItems().add(Event.Type.MODEL_STATUS_CHANGED);
event.getItems().add(Event.Type.RECORDING_STATUS_CHANGED); event.getItems().add(Event.Type.RECORDING_STATUS_CHANGED);
event.getItems().add(Event.Type.NO_SPACE_LEFT);
event.setOnAction(evt -> modelState.setVisible(event.getSelectionModel().getSelectedItem() == Event.Type.MODEL_STATUS_CHANGED)); event.setOnAction(evt -> modelState.setVisible(event.getSelectionModel().getSelectedItem() == Event.Type.MODEL_STATUS_CHANGED));
event.getSelectionModel().select(Event.Type.MODEL_STATUS_CHANGED); event.getSelectionModel().select(Event.Type.MODEL_STATUS_CHANGED);
layout.add(event, 1, row++); layout.add(event, 1, row++);
event.getSelectionModel().selectedItemProperty().addListener((obs, oldV, newV) -> {
boolean modelRelatedStuffDisabled = false;
if(newV == Event.Type.NO_SPACE_LEFT) {
modelRelatedStuffDisabled = true;
modelSelectionPane.selectAll();
}
modelState.setDisable(modelRelatedStuffDisabled);
recordingState.setDisable(modelRelatedStuffDisabled);
modelSelectionPane.setDisable(modelRelatedStuffDisabled);
});
layout.add(new Label("State"), 0, row); layout.add(new Label("State"), 0, row);
modelState.getItems().clear(); modelState.getItems().clear();
modelState.getItems().addAll(Model.State.values()); modelState.getItems().addAll(Model.State.values());

View File

@ -118,4 +118,8 @@ public class ListSelectionPane<T extends Comparable<T>> extends GridPane {
public boolean isAllSelected() { public boolean isAllSelected() {
return selectAll.isSelected(); return selectAll.isSelected();
} }
public void selectAll() {
selectAll.setSelected(true);
}
} }

View File

@ -1,9 +1,13 @@
package ctbrec.ui.sites.cam4; package ctbrec.ui.sites.cam4;
import static ctbrec.Model.State.*;
import static ctbrec.io.HttpConstants.*;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
@ -64,7 +68,11 @@ public class Cam4UpdateService extends PaginatedScheduledService {
if(loginRequired) { if(loginRequired) {
SiteUiFactory.getUi(site).login(); SiteUiFactory.getUi(site).login();
} }
Request request = new Request.Builder().url(url).build(); Request request = new Request.Builder()
.url(url)
.header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
.build();
try (Response response = site.getHttpClient().execute(request)) { try (Response response = site.getHttpClient().execute(request)) {
if (response.isSuccessful()) { if (response.isSuccessful()) {
JSONObject json = new JSONObject(response.body().string()); JSONObject json = new JSONObject(response.body().string());
@ -76,11 +84,15 @@ public class Cam4UpdateService extends PaginatedScheduledService {
Element profileLink = HtmlParser.getTag(boxHtml, "a.profile-preview"); Element profileLink = HtmlParser.getTag(boxHtml, "a.profile-preview");
String path = profileLink.attr("href"); String path = profileLink.attr("href");
String slug = path.substring(1); String slug = path.substring(1);
Cam4Model model = (Cam4Model) site.createModel(slug); Cam4Model model = site.createModel(slug);
String playlistUrl = profileLink.attr("data-hls-preview-url"); String playlistUrl = profileLink.attr("data-hls-preview-url");
model.setPlaylistUrl(playlistUrl); model.setPlaylistUrl(playlistUrl);
model.setPreview("https://snapshots.xcdnpro.com/thumbnails/" + model.getName() + "?s=" + System.currentTimeMillis()); model.setPreview("https://snapshots.xcdnpro.com/thumbnails/" + model.getName() + "?s=" + System.currentTimeMillis());
model.setDescription(parseDesription(boxHtml)); model.setDescription(parseDesription(boxHtml));
model.setOnlineState(ONLINE);
if(boxHtml.contains("In private show")) {
model.setOnlineState(PRIVATE);
}
models.add(model); models.add(model);
} }
return models; return models;

View File

@ -1,5 +1,6 @@
package ctbrec.ui.sites.streamate; package ctbrec.ui.sites.streamate;
import static ctbrec.Model.State.*;
import static ctbrec.io.HttpConstants.*; import static ctbrec.io.HttpConstants.*;
import java.io.IOException; import java.io.IOException;
@ -81,6 +82,7 @@ public class StreamateFollowedService extends PaginatedScheduledService {
model.setPreview(p.getString("thumbnail")); model.setPreview(p.getString("thumbnail"));
boolean online = p.optBoolean("online") && notPrivateEtc(p); boolean online = p.optBoolean("online") && notPrivateEtc(p);
model.setOnline(online); model.setOnline(online);
model.setOnlineState(online ? ONLINE : OFFLINE);
if (online == showOnline) { if (online == showOnline) {
models.add(model); models.add(model);
} }

View File

@ -1,5 +1,7 @@
package ctbrec.ui.sites.streamate; package ctbrec.ui.sites.streamate;
import static ctbrec.Model.State.*;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -52,6 +54,7 @@ public class StreamateUpdateService extends PaginatedScheduledService {
List<Model> models = new ArrayList<>(); List<Model> models = new ArrayList<>();
String content = response.body().string(); String content = response.body().string();
JSONObject json = new JSONObject(content); JSONObject json = new JSONObject(content);
System.err.println(json.toString(2));
JSONArray performers = json.getJSONArray("performers"); JSONArray performers = json.getJSONArray("performers");
for (int i = 0; i < performers.length(); i++) { for (int i = 0; i < performers.length(); i++) {
JSONObject p = performers.getJSONObject(i); JSONObject p = performers.getJSONObject(i);
@ -59,7 +62,9 @@ public class StreamateUpdateService extends PaginatedScheduledService {
StreamateModel model = (StreamateModel) streamate.createModel(nickname); StreamateModel model = (StreamateModel) streamate.createModel(nickname);
model.setId(p.getLong("id")); model.setId(p.getLong("id"));
model.setPreview(p.getString("thumbnail")); model.setPreview(p.getString("thumbnail"));
model.setOnline(p.optBoolean("online")); boolean online = p.optBoolean("online");
model.setOnline(online);
model.setOnlineState(online ? ONLINE : OFFLINE);
// TODO figure out, what all the states mean // TODO figure out, what all the states mean
// liveState {} // liveState {}
// exclusiveShow false // exclusiveShow false
@ -69,6 +74,11 @@ public class StreamateUpdateService extends PaginatedScheduledService {
// preGoldShow true // preGoldShow true
// privateChat false // privateChat false
// specialShow false // specialShow false
if (p.optBoolean("onBreak")) {
model.setOnlineState(AWAY);
} else if (p.optBoolean("goldShow") || p.optBoolean("privateChat") || p.optBoolean("exclusiveShow")) {
model.setOnlineState(PRIVATE);
}
models.add(model); models.add(model);
} }
return models; return models;

View File

@ -1,5 +1,6 @@
package ctbrec.ui.sites.stripchat; package ctbrec.ui.sites.stripchat;
import static ctbrec.Model.State.*;
import static ctbrec.io.HttpConstants.*; import static ctbrec.io.HttpConstants.*;
import java.io.IOException; import java.io.IOException;
@ -68,6 +69,7 @@ public class StripchatFollowedUpdateService extends PaginatedScheduledService {
try (Response response = stripchat.getHttpClient().execute(request)) { try (Response response = stripchat.getHttpClient().execute(request)) {
if (response.isSuccessful()) { if (response.isSuccessful()) {
JSONObject json = new JSONObject(response.body().string()); JSONObject json = new JSONObject(response.body().string());
System.err.println(json.toString(2));
if (json.has("models")) { if (json.has("models")) {
JSONArray users = json.getJSONArray("models"); JSONArray users = json.getJSONArray("models");
for (int i = 0; i < users.length(); i++) { for (int i = 0; i < users.length(); i++) {
@ -75,6 +77,7 @@ public class StripchatFollowedUpdateService extends PaginatedScheduledService {
StripchatModel model = stripchat.createModel(user.optString("username")); StripchatModel model = stripchat.createModel(user.optString("username"));
model.setDescription(user.optString("description")); model.setDescription(user.optString("description"));
model.setPreview(user.optString("previewUrlThumbBig")); model.setPreview(user.optString("previewUrlThumbBig"));
model.setOnlineState(mapStatus(user.optString("status")));
models.add(model); models.add(model);
} }
} }
@ -114,4 +117,17 @@ public class StripchatFollowedUpdateService extends PaginatedScheduledService {
} }
}; };
} }
protected ctbrec.Model.State mapStatus(String status) {
switch (status) {
case "public":
return ONLINE;
case "idle":
return AWAY;
case "off":
return OFFLINE;
default:
return UNKNOWN;
}
}
} }

View File

@ -75,6 +75,7 @@ public class StripchatUpdateService extends PaginatedScheduledService {
model.setDescription(""); model.setDescription("");
model.setPreview(jsonModel.optString("snapshotUrl")); model.setPreview(jsonModel.optString("snapshotUrl"));
model.setDisplayName(model.getName()); model.setDisplayName(model.getName());
model.setOnlineState(Model.State.ONLINE);
models.add(model); models.add(model);
} catch (Exception e) { } catch (Exception e) {
LOG.warn("Couldn't parse one of the models: {}", jsonModel, e); LOG.warn("Couldn't parse one of the models: {}", jsonModel, e);

View File

@ -18,7 +18,12 @@ public abstract class Event {
/** /**
* This event is fired whenever the state of a recording changes. * This event is fired whenever the state of a recording changes.
*/ */
RECORDING_STATUS_CHANGED("recording status changed"); RECORDING_STATUS_CHANGED("recording status changed"),
/**
* This event is fired when the disk space is exhausted
*/
NO_SPACE_LEFT("no space left");
private String desc; private String desc;

View File

@ -0,0 +1,17 @@
package ctbrec.event;
import ctbrec.event.EventHandlerConfiguration.PredicateConfiguration;
public class MatchAllPredicate extends EventPredicate {
@Override
public boolean test(Event evt) {
return true;
}
@Override
public void configure(PredicateConfiguration pc) {
// noop
}
}

View File

@ -0,0 +1,25 @@
package ctbrec.event;
public class NoSpaceLeftEvent extends Event {
@Override
public Type getType() {
return Type.NO_SPACE_LEFT;
}
@Override
public String getName() {
return "No space left on device";
}
@Override
public String getDescription() {
return "No space left on device";
}
@Override
public String[] getExecutionParams() {
return new String[0];
}
}

View File

@ -49,6 +49,7 @@ import ctbrec.Recording.State;
import ctbrec.event.Event; import ctbrec.event.Event;
import ctbrec.event.EventBusHolder; import ctbrec.event.EventBusHolder;
import ctbrec.event.ModelIsOnlineEvent; import ctbrec.event.ModelIsOnlineEvent;
import ctbrec.event.NoSpaceLeftEvent;
import ctbrec.event.RecordingStateChangedEvent; import ctbrec.event.RecordingStateChangedEvent;
import ctbrec.io.HttpClient; import ctbrec.io.HttpClient;
import ctbrec.recorder.download.Download; import ctbrec.recorder.download.Download;
@ -111,6 +112,7 @@ public class NextGenLocalRecorder implements Recorder {
if (!recordingProcesses.isEmpty() && !enoughSpaceForRecording()) { if (!recordingProcesses.isEmpty() && !enoughSpaceForRecording()) {
LOG.info("No space left -> Stopping all recordings"); LOG.info("No space left -> Stopping all recordings");
stopRecordingProcesses(); stopRecordingProcesses();
EventBusHolder.BUS.post(new NoSpaceLeftEvent());
} }
} catch (IOException e) { } catch (IOException e) {
LOG.error("Couldn't check space left on device", e); LOG.error("Couldn't check space left on device", e);
@ -577,7 +579,7 @@ public class NextGenLocalRecorder implements Recorder {
boolean enoughSpaceForRecording() throws IOException { boolean enoughSpaceForRecording() throws IOException {
long minimum = config.getSettings().minimumSpaceLeftInBytes; long minimum = config.getSettings().minimumSpaceLeftInBytes;
if (minimum == 0) { // 0 means don't check if (minimum == 0) { // 0 means don't check
return true; return getFreeSpaceBytes() > 100 * 1024 * 1024; // leave at least 100 MiB free
} else { } else {
return getFreeSpaceBytes() > minimum; return getFreeSpaceBytes() > minimum;
} }
@ -678,8 +680,7 @@ public class NextGenLocalRecorder implements Recorder {
@Override @Override
public void setNote(Recording rec, String note) throws IOException { public void setNote(Recording rec, String note) throws IOException {
rec.setNote(note); recordingManager.setNote(rec, note);
recordingManager.saveRecording(rec);
} }
@Override @Override

View File

@ -11,6 +11,8 @@ import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -217,8 +219,9 @@ public class RecordingManager {
public void pin(Recording recording) throws IOException { public void pin(Recording recording) throws IOException {
recordingsLock.lock(); recordingsLock.lock();
try { try {
recording.setPinned(true); Recording local = getRecording(recording);
saveRecording(recording); local.setPinned(true);
saveRecording(local);
} finally { } finally {
recordingsLock.unlock(); recordingsLock.unlock();
} }
@ -227,10 +230,31 @@ public class RecordingManager {
public void unpin(Recording recording) throws IOException { public void unpin(Recording recording) throws IOException {
recordingsLock.lock(); recordingsLock.lock();
try { try {
recording.setPinned(false); Recording local = getRecording(recording);
saveRecording(recording); local.setPinned(false);
saveRecording(local);
} finally { } finally {
recordingsLock.unlock(); recordingsLock.unlock();
} }
} }
public void setNote(Recording rec, String note) throws IOException {
recordingsLock.lock();
try {
Recording local = getRecording(rec);
local.setNote(note);
saveRecording(local);
} finally {
recordingsLock.unlock();
}
}
private Recording getRecording(Recording rec) {
for (Recording r : getAll()) {
if(Objects.equals(r, rec)) {
return r;
}
}
throw new NoSuchElementException("Recording not found");
}
} }

View File

@ -24,6 +24,7 @@ import ctbrec.Hmac;
import ctbrec.Model; import ctbrec.Model;
import ctbrec.Recording; import ctbrec.Recording;
import ctbrec.event.EventBusHolder; import ctbrec.event.EventBusHolder;
import ctbrec.event.NoSpaceLeftEvent;
import ctbrec.event.RecordingStateChangedEvent; import ctbrec.event.RecordingStateChangedEvent;
import ctbrec.io.BandwidthMeter; import ctbrec.io.BandwidthMeter;
import ctbrec.io.HttpClient; import ctbrec.io.HttpClient;
@ -57,6 +58,7 @@ public class RemoteRecorder implements Recorder {
private List<Site> sites; private List<Site> sites;
private long spaceTotal = -1; private long spaceTotal = -1;
private long spaceFree = -1; private long spaceFree = -1;
private boolean noSpaceLeftDetected = false;
private Config config; private Config config;
private HttpClient client; private HttpClient client;
@ -184,6 +186,7 @@ public class RemoteRecorder implements Recorder {
private static final String COULDNT_SYNCHRONIZE_WITH_SERVER = "Couldn't synchronize with server"; private static final String COULDNT_SYNCHRONIZE_WITH_SERVER = "Couldn't synchronize with server";
private static final String COULDNT_SYNCHRONIZE_WITH_SERVER_HTTP_STATUS = "Couldn't synchronize with server. HTTP status: {} - {}"; private static final String COULDNT_SYNCHRONIZE_WITH_SERVER_HTTP_STATUS = "Couldn't synchronize with server. HTTP status: {} - {}";
private volatile boolean running = false; private volatile boolean running = false;
private static final long ONE_HUNDRED_MIB = 100 * 1024 * 1024;
public SyncThread() { public SyncThread() {
setName("RemoteRecorder SyncThread"); setName("RemoteRecorder SyncThread");
@ -218,6 +221,16 @@ public class RemoteRecorder implements Recorder {
long throughput = resp.getLong("throughput"); long throughput = resp.getLong("throughput");
Duration timeframe = Duration.ofSeconds(resp.getInt("throughputTimeframe")); Duration timeframe = Duration.ofSeconds(resp.getInt("throughputTimeframe"));
BandwidthMeter.setThroughput(throughput, timeframe); BandwidthMeter.setThroughput(throughput, timeframe);
long minimumSpaceLeftInBytes = resp.optLong("minimumSpaceLeftInBytes");
if (minimumSpaceLeftInBytes > 0 && minimumSpaceLeftInBytes >= spaceFree
|| minimumSpaceLeftInBytes == 0 && spaceFree < ONE_HUNDRED_MIB) {
if (!noSpaceLeftDetected) {
EventBusHolder.BUS.post(new NoSpaceLeftEvent());
}
noSpaceLeftDetected = true;
} else {
noSpaceLeftDetected = false;
}
} else { } else {
LOG.error(COULDNT_SYNCHRONIZE_WITH_SERVER_HTTP_STATUS, response.code(), json); LOG.error(COULDNT_SYNCHRONIZE_WITH_SERVER_HTTP_STATUS, response.code(), json);
} }
@ -319,6 +332,18 @@ public class RemoteRecorder implements Recorder {
} }
} }
} }
// fire recording started event
List<Recording> justStarted = new ArrayList<>(newRecordings);
justStarted.removeAll(recordings);
for (Recording recording : justStarted) {
if (recording.getStatus() == Recording.State.RECORDING) {
File file = new File(recording.getPath());
RecordingStateChangedEvent evt = new RecordingStateChangedEvent(file, recording.getStatus(), recording.getModel(),
recording.getStartDate());
EventBusHolder.BUS.post(evt);
}
}
recordings = newRecordings; recordings = newRecordings;
} else { } else {
LOG.error(SERVER_RETURNED_ERROR, resp.status, resp.msg); LOG.error(SERVER_RETURNED_ERROR, resp.status, resp.msg);

View File

@ -1,5 +1,6 @@
package ctbrec.sites.manyvids; package ctbrec.sites.manyvids;
import static ctbrec.StringUtil.*;
import static ctbrec.io.HttpConstants.*; import static ctbrec.io.HttpConstants.*;
import static ctbrec.sites.manyvids.MVLive.*; import static ctbrec.sites.manyvids.MVLive.*;
@ -182,7 +183,7 @@ public class MVLiveClient {
String respJson = jsonArray.getString(i); String respJson = jsonArray.getString(i);
JSONObject response = new JSONObject(respJson); JSONObject response = new JSONObject(respJson);
String address = response.optString("address"); String address = response.optString("address");
if (!address.isBlank()) { if (isNotBlank(address)) {
Message message = futureResponses.get(address); Message message = futureResponses.get(address);
if (message != null) { if (message != null) {
message.handleResponse(response); message.handleResponse(response);

View File

@ -320,6 +320,7 @@ public class MyFreeCamsClient {
LOG.error("Error while decoding ctxenc URL", e); LOG.error("Error while decoding ctxenc URL", e);
} catch (Exception e) { } catch (Exception e) {
LOG.error("Exception occured while processing websocket message {}", msgBuffer, e); LOG.error("Exception occured while processing websocket message {}", msgBuffer, e);
ws.close(1000, "");
} }
} }

View File

@ -4,6 +4,7 @@ import static ctbrec.Model.State.*;
import static ctbrec.io.HttpConstants.*; import static ctbrec.io.HttpConstants.*;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.Duration; import java.time.Duration;
@ -52,23 +53,21 @@ public class Showup extends AbstractSite {
@Override @Override
public Model createModel(String name) { public Model createModel(String name) {
// try {
// for (Model m : getModelList()) {
// if (Objects.equal(m.getName(), name)) {
// return m;
// }
// }
// } catch (IOException e) {
// throw new ModelNotFoundException(name, e);
// }
// throw new ModelNotFoundException(name);
ShowupModel model = new ShowupModel(); ShowupModel model = new ShowupModel();
model.setSite(this); model.setSite(this);
model.setName(name); model.setName(name);
model.setUrl(BASE_URL + '/' + URLEncoder.encode(name, StandardCharsets.UTF_8)); model.setUrl(BASE_URL + '/' + safeUrlEncode(name));
return model; return model;
} }
private String safeUrlEncode(String s) {
try {
return URLEncoder.encode(s, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
return s;
}
}
public List<Model> getModelList() throws IOException { public List<Model> getModelList() throws IOException {
if(Duration.between(lastModelListUpdate, Instant.now()).getSeconds() > 10) { if(Duration.between(lastModelListUpdate, Instant.now()).getSeconds() > 10) {
lastModelListUpdate = Instant.now(); lastModelListUpdate = Instant.now();

View File

@ -8,6 +8,7 @@ import org.json.JSONObject;
import org.junit.Test; import org.junit.Test;
import ctbrec.ReflectionUtil; import ctbrec.ReflectionUtil;
import ctbrec.StringUtil;
public class MyFreeCamsClientTest { public class MyFreeCamsClientTest {
@ -22,7 +23,7 @@ public class MyFreeCamsClientTest {
assertEquals(439895060, msg.getReceiver()); assertEquals(439895060, msg.getReceiver());
assertEquals(1, msg.getArg1()); assertEquals(1, msg.getArg1());
assertEquals(0, msg.getArg2()); assertEquals(0, msg.getArg2());
assertTrue(msg.getMessage().isBlank()); assertTrue(StringUtil.isBlank(msg.getMessage()));
input = new StringBuilder("00207820 439461784 439895060 12 507930 %7B%22lv%22%3A4%2C%22nm%22%3A%22Nivea%22%2C%22pid%22%3A1%2C%22sid%22%3A439461784%2C%22uid%22%3A507930%2C%22vs%22%3A12%2C%22u%22%3A%7B%22age%22%3A33%2C%22avatar%22%3A1%2C%22blurb%22%3A%22I%20love%20when%20my%20nose%20touch%20your%20belly%20when%20I%20do%20you%20a%20blowjob!%20When%20I%20look%20into%20your%20horny%20eyes%20when%22%2C%22camserv%22%3A1367%2C%22chat_color%22%3A%22FF0000%22%2C%22chat_font%22%3A0%2C%22chat_opt%22%3A1%2C%22ethnic%22%3A%22Caucasian%22%2C%22photos%22%3A74%2C%22profile%22%3A1%2C%22status%22%3A%22%22%7D%2C%22m%22%3A%7B%22camscore%22%3A7505.700%2C%22continent%22%3A%22EU%22%2C%22flags%22%3A605224%2C%22hidecs%22%3Atrue%2C%22kbit%22%3A0%2C%22lastnews%22%3A0%2C%22mg%22%3A0%2C%22missmfc%22%3A2%2C%22new_model%22%3A0%2C%22rank%22%3A0%2C%22rc%22%3A20%2C%22sfw%22%3A0%2C%22topic%22%3A%22hi%253A)%255Bnone%255D-Topless%252C500-snap4life%252C50-spanks%252C180-flash%252C700-10%2520mins%2520of%2520Nora%2520fun%252C666-shot%252C27%252C270%2520%253C3%22%7D%2C%22x%22%3A%7B%22fcext%22%3A%7B%22sm%22%3A%22%22%2C%22sfw%22%3A0%7D%2C%22share%22%3A%7B%22follows%22%3A11%2C%22albums%22%3A0%2C%22clubs%22%3A0%2C%22tm_album%22%3A0%2C%22collections%22%3A0%2C%22stores%22%3A0%2C%22goals%22%3A0%2C%22polls%22%3A0%2C%22things%22%3A0%2C%22recent_album_tm%22%3A0%2C%22recent_club_tm%22%3A0%2C%22recent_collection_tm%22%3A0%2C%22recent_goal_tm%22%3A0%2C%22recent_item_tm%22%3A0%2C%22recent_poll_tm%22%3A0%2C%22recent_story_tm%22%3A0%2C%22recent_album_thumb%22%3A%22%22%2C%22recent_club_thumb%22%3A%22%22%2C%22recent_collection_thumb%22%3A%22%22%2C%22recent_goal_thumb%22%3A%22%22%2C%22recent_item_thumb%22%3A%22%22%2C%22recent_poll_thumb%22%3A%22%22%2C%22recent_story_thumb%22%3A%22%22%2C%22recent_album_title%22%3A%22%22%2C%22recent_club_title%22%3A%22%22%2C%22recent_collection_title%22%3A%22%22%2C%22recent_goal_title%22%3A%22%22%2C%22recent_item_title%22%3A%22%22%2C%22recent_poll_title%22%3A%22%22%2C%22recent_story_title%22%3A%22%22%2C%22recent_album_slug%22%3A%22%22%2C%22recent_collection_slug%22%3A%22%22%2C%22tipmenus%22%3A0%7D%7D%7D"); input = new StringBuilder("00207820 439461784 439895060 12 507930 %7B%22lv%22%3A4%2C%22nm%22%3A%22Nivea%22%2C%22pid%22%3A1%2C%22sid%22%3A439461784%2C%22uid%22%3A507930%2C%22vs%22%3A12%2C%22u%22%3A%7B%22age%22%3A33%2C%22avatar%22%3A1%2C%22blurb%22%3A%22I%20love%20when%20my%20nose%20touch%20your%20belly%20when%20I%20do%20you%20a%20blowjob!%20When%20I%20look%20into%20your%20horny%20eyes%20when%22%2C%22camserv%22%3A1367%2C%22chat_color%22%3A%22FF0000%22%2C%22chat_font%22%3A0%2C%22chat_opt%22%3A1%2C%22ethnic%22%3A%22Caucasian%22%2C%22photos%22%3A74%2C%22profile%22%3A1%2C%22status%22%3A%22%22%7D%2C%22m%22%3A%7B%22camscore%22%3A7505.700%2C%22continent%22%3A%22EU%22%2C%22flags%22%3A605224%2C%22hidecs%22%3Atrue%2C%22kbit%22%3A0%2C%22lastnews%22%3A0%2C%22mg%22%3A0%2C%22missmfc%22%3A2%2C%22new_model%22%3A0%2C%22rank%22%3A0%2C%22rc%22%3A20%2C%22sfw%22%3A0%2C%22topic%22%3A%22hi%253A)%255Bnone%255D-Topless%252C500-snap4life%252C50-spanks%252C180-flash%252C700-10%2520mins%2520of%2520Nora%2520fun%252C666-shot%252C27%252C270%2520%253C3%22%7D%2C%22x%22%3A%7B%22fcext%22%3A%7B%22sm%22%3A%22%22%2C%22sfw%22%3A0%7D%2C%22share%22%3A%7B%22follows%22%3A11%2C%22albums%22%3A0%2C%22clubs%22%3A0%2C%22tm_album%22%3A0%2C%22collections%22%3A0%2C%22stores%22%3A0%2C%22goals%22%3A0%2C%22polls%22%3A0%2C%22things%22%3A0%2C%22recent_album_tm%22%3A0%2C%22recent_club_tm%22%3A0%2C%22recent_collection_tm%22%3A0%2C%22recent_goal_tm%22%3A0%2C%22recent_item_tm%22%3A0%2C%22recent_poll_tm%22%3A0%2C%22recent_story_tm%22%3A0%2C%22recent_album_thumb%22%3A%22%22%2C%22recent_club_thumb%22%3A%22%22%2C%22recent_collection_thumb%22%3A%22%22%2C%22recent_goal_thumb%22%3A%22%22%2C%22recent_item_thumb%22%3A%22%22%2C%22recent_poll_thumb%22%3A%22%22%2C%22recent_story_thumb%22%3A%22%22%2C%22recent_album_title%22%3A%22%22%2C%22recent_club_title%22%3A%22%22%2C%22recent_collection_title%22%3A%22%22%2C%22recent_goal_title%22%3A%22%22%2C%22recent_item_title%22%3A%22%22%2C%22recent_poll_title%22%3A%22%22%2C%22recent_story_title%22%3A%22%22%2C%22recent_album_slug%22%3A%22%22%2C%22recent_collection_slug%22%3A%22%22%2C%22tipmenus%22%3A0%7D%7D%7D");
msg = ReflectionUtil.call(client, "parseMessage", input); msg = ReflectionUtil.call(client, "parseMessage", input);

View File

@ -21,6 +21,7 @@ import org.slf4j.LoggerFactory;
import com.squareup.moshi.JsonAdapter; import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.Moshi; import com.squareup.moshi.Moshi;
import ctbrec.Config;
import ctbrec.Model; import ctbrec.Model;
import ctbrec.Recording; import ctbrec.Recording;
import ctbrec.io.BandwidthMeter; import ctbrec.io.BandwidthMeter;
@ -211,6 +212,7 @@ public class RecorderServlet extends AbstractCtbrecServlet {
jsonResponse.put("spaceFree", recorder.getFreeSpaceBytes()); jsonResponse.put("spaceFree", recorder.getFreeSpaceBytes());
jsonResponse.put("throughput", BandwidthMeter.getThroughput()); jsonResponse.put("throughput", BandwidthMeter.getThroughput());
jsonResponse.put("throughputTimeframe", BandwidthMeter.MEASURE_TIMEFRAME.getSeconds()); jsonResponse.put("throughputTimeframe", BandwidthMeter.MEASURE_TIMEFRAME.getSeconds());
jsonResponse.put("minimumSpaceLeftInBytes", Config.getInstance().getSettings().minimumSpaceLeftInBytes);
resp.getWriter().write(jsonResponse.toString()); resp.getWriter().write(jsonResponse.toString());
break; break;
case "changePriority": case "changePriority":