forked from j62/ctbrec
Merge branch 'dev' into v4
This commit is contained in:
commit
fa7f1e5f57
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -1,6 +1,20 @@
|
||||||
|
3.13.0
|
||||||
|
========================
|
||||||
|
* Added "Recently watched" tab. Can be disabled in Settings -> General
|
||||||
|
* Recording size now takes all associated files into account
|
||||||
|
* Removed restriction of download thread pool size (was 100 before)
|
||||||
|
|
||||||
|
3.12.2
|
||||||
|
========================
|
||||||
|
* Fix: Some Cam4 URLs were broken
|
||||||
|
* Fix: Cam4 search didn't work
|
||||||
|
* Stop hlsdl if the recording size didn't change for 90 seconds
|
||||||
|
|
||||||
3.12.1
|
3.12.1
|
||||||
========================
|
========================
|
||||||
* Fix: "Resume all" started the recordings of models marked for later recording
|
* Fix: "Resume all" started the recordings of models marked for later recording
|
||||||
|
* Fix: Login dialogs don't open
|
||||||
|
* Use 16:9 thumbnail format for MFC
|
||||||
|
|
||||||
3.12.0
|
3.12.0
|
||||||
========================
|
========================
|
||||||
|
|
|
@ -63,6 +63,7 @@ import ctbrec.ui.news.NewsTab;
|
||||||
import ctbrec.ui.settings.SettingsTab;
|
import ctbrec.ui.settings.SettingsTab;
|
||||||
import ctbrec.ui.tabs.DonateTabFx;
|
import ctbrec.ui.tabs.DonateTabFx;
|
||||||
import ctbrec.ui.tabs.HelpTab;
|
import ctbrec.ui.tabs.HelpTab;
|
||||||
|
import ctbrec.ui.tabs.RecentlyWatchedTab;
|
||||||
import ctbrec.ui.tabs.RecordedTab;
|
import ctbrec.ui.tabs.RecordedTab;
|
||||||
import ctbrec.ui.tabs.RecordingsTab;
|
import ctbrec.ui.tabs.RecordingsTab;
|
||||||
import ctbrec.ui.tabs.SiteTab;
|
import ctbrec.ui.tabs.SiteTab;
|
||||||
|
@ -216,6 +217,9 @@ public class CamrecApplication extends Application {
|
||||||
tabPane.getTabs().add(modelsTab);
|
tabPane.getTabs().add(modelsTab);
|
||||||
recordingsTab = new RecordingsTab("Recordings", recorder, config);
|
recordingsTab = new RecordingsTab("Recordings", recorder, config);
|
||||||
tabPane.getTabs().add(recordingsTab);
|
tabPane.getTabs().add(recordingsTab);
|
||||||
|
if (config.getSettings().recentlyWatched) {
|
||||||
|
tabPane.getTabs().add(new RecentlyWatchedTab(recorder, sites));
|
||||||
|
}
|
||||||
tabPane.getTabs().add(new SettingsTab(sites, recorder));
|
tabPane.getTabs().add(new SettingsTab(sites, recorder));
|
||||||
tabPane.getTabs().add(new NewsTab());
|
tabPane.getTabs().add(new NewsTab());
|
||||||
tabPane.getTabs().add(new DonateTabFx());
|
tabPane.getTabs().add(new DonateTabFx());
|
||||||
|
@ -299,8 +303,11 @@ public class CamrecApplication extends Application {
|
||||||
|
|
||||||
final boolean immediately = shutdownNow;
|
final boolean immediately = shutdownNow;
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
modelsTab.saveState();
|
for (Tab tab : tabPane.getTabs()) {
|
||||||
recordingsTab.saveState();
|
if (tab instanceof ShutdownListener) {
|
||||||
|
((ShutdownListener) tab).onShutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
onlineMonitor.shutdown();
|
onlineMonitor.shutdown();
|
||||||
recorder.shutdown(immediately);
|
recorder.shutdown(immediately);
|
||||||
for (Site site : sites) {
|
for (Site site : sites) {
|
||||||
|
|
|
@ -28,11 +28,10 @@ public class ExternalBrowser implements AutoCloseable {
|
||||||
private static final ExternalBrowser INSTANCE = new ExternalBrowser();
|
private static final ExternalBrowser INSTANCE = new ExternalBrowser();
|
||||||
private Lock lock = new ReentrantLock();
|
private Lock lock = new ReentrantLock();
|
||||||
|
|
||||||
private Process p;
|
|
||||||
private Consumer<String> messageListener;
|
private Consumer<String> messageListener;
|
||||||
private InputStream in;
|
private InputStream in;
|
||||||
private OutputStream out;
|
private OutputStream out;
|
||||||
private Socket socket;
|
private Socket socket; // NOSONAR
|
||||||
private Thread reader;
|
private Thread reader;
|
||||||
private volatile boolean stopped = true;
|
private volatile boolean stopped = true;
|
||||||
private volatile boolean browserReady = false;
|
private volatile boolean browserReady = false;
|
||||||
|
@ -53,10 +52,10 @@ public class ExternalBrowser implements AutoCloseable {
|
||||||
|
|
||||||
File configDir = new File(Config.getInstance().getConfigDir(), "ctbrec-minimal-browser");
|
File configDir = new File(Config.getInstance().getConfigDir(), "ctbrec-minimal-browser");
|
||||||
String[] cmdline = OS.getBrowserCommand(configDir.getCanonicalPath());
|
String[] cmdline = OS.getBrowserCommand(configDir.getCanonicalPath());
|
||||||
p = new ProcessBuilder(cmdline).start();
|
Process p = new ProcessBuilder(cmdline).start();
|
||||||
if (LOG.isTraceEnabled()) {
|
if (LOG.isTraceEnabled()) {
|
||||||
new Thread(new StreamRedirector(p.getInputStream(), System.out)).start();
|
new Thread(new StreamRedirector(p.getInputStream(), System.out)).start(); // NOSONAR
|
||||||
new Thread(new StreamRedirector(p.getErrorStream(), System.err)).start();
|
new Thread(new StreamRedirector(p.getErrorStream(), System.err)).start(); // NOSONAR
|
||||||
} else {
|
} else {
|
||||||
new Thread(new StreamRedirector(p.getInputStream(), OutputStream.nullOutputStream())).start();
|
new Thread(new StreamRedirector(p.getInputStream(), OutputStream.nullOutputStream())).start();
|
||||||
new Thread(new StreamRedirector(p.getErrorStream(), OutputStream.nullOutputStream())).start();
|
new Thread(new StreamRedirector(p.getErrorStream(), OutputStream.nullOutputStream())).start();
|
||||||
|
@ -81,7 +80,6 @@ public class ExternalBrowser implements AutoCloseable {
|
||||||
LOG.debug("Waiting for browser to terminate");
|
LOG.debug("Waiting for browser to terminate");
|
||||||
p.waitFor();
|
p.waitFor();
|
||||||
int exitValue = p.exitValue();
|
int exitValue = p.exitValue();
|
||||||
p = null;
|
|
||||||
reader = null;
|
reader = null;
|
||||||
in = null;
|
in = null;
|
||||||
out = null;
|
out = null;
|
||||||
|
@ -119,7 +117,6 @@ public class ExternalBrowser implements AutoCloseable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void executeJavaScript(String javaScript) throws IOException {
|
public void executeJavaScript(String javaScript) throws IOException {
|
||||||
//LOG.debug("Executing JS {}", javaScript);
|
|
||||||
JSONObject script = new JSONObject();
|
JSONObject script = new JSONObject();
|
||||||
script.put("execute", javaScript);
|
script.put("execute", javaScript);
|
||||||
out.write(script.toString().getBytes(UTF_8));
|
out.write(script.toString().getBytes(UTF_8));
|
||||||
|
@ -141,8 +138,7 @@ public class ExternalBrowser implements AutoCloseable {
|
||||||
|
|
||||||
private void readBrowserOutput() {
|
private void readBrowserOutput() {
|
||||||
LOG.debug("Browser output reader started");
|
LOG.debug("Browser output reader started");
|
||||||
try {
|
try (BufferedReader br = new BufferedReader(new InputStreamReader(in))) {
|
||||||
BufferedReader br = new BufferedReader(new InputStreamReader(in));
|
|
||||||
String line;
|
String line;
|
||||||
synchronized (browserReadyLock) {
|
synchronized (browserReadyLock) {
|
||||||
browserReady = true;
|
browserReady = true;
|
||||||
|
@ -150,11 +146,8 @@ public class ExternalBrowser implements AutoCloseable {
|
||||||
}
|
}
|
||||||
while( !Thread.interrupted() && (line = br.readLine()) != null ) {
|
while( !Thread.interrupted() && (line = br.readLine()) != null ) {
|
||||||
LOG.debug("Browser output: {}", line);
|
LOG.debug("Browser output: {}", line);
|
||||||
if(!line.startsWith("{")) {
|
if (line.startsWith("{") && messageListener != null) {
|
||||||
} else {
|
messageListener.accept(line);
|
||||||
if(messageListener != null) {
|
|
||||||
messageListener.accept(line);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|
|
@ -25,10 +25,12 @@ import ctbrec.Config;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.OS;
|
import ctbrec.OS;
|
||||||
import ctbrec.Recording;
|
import ctbrec.Recording;
|
||||||
|
import ctbrec.event.EventBusHolder;
|
||||||
import ctbrec.io.StreamRedirector;
|
import ctbrec.io.StreamRedirector;
|
||||||
import ctbrec.io.UrlUtil;
|
import ctbrec.io.UrlUtil;
|
||||||
import ctbrec.recorder.download.StreamSource;
|
import ctbrec.recorder.download.StreamSource;
|
||||||
import ctbrec.ui.controls.Dialogs;
|
import ctbrec.ui.controls.Dialogs;
|
||||||
|
import ctbrec.ui.event.PlayerStartedEvent;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
|
|
||||||
public class Player {
|
public class Player {
|
||||||
|
@ -85,6 +87,7 @@ public class Player {
|
||||||
}
|
}
|
||||||
String playlistUrl = getPlaylistUrl(model);
|
String playlistUrl = getPlaylistUrl(model);
|
||||||
LOG.debug("Playing {}", playlistUrl);
|
LOG.debug("Playing {}", playlistUrl);
|
||||||
|
EventBusHolder.BUS.post(new PlayerStartedEvent(model));
|
||||||
return Player.play(playlistUrl, async);
|
return Player.play(playlistUrl, async);
|
||||||
} else {
|
} else {
|
||||||
Dialogs.showError(scene, "Room not public", "Room is currently not public", null);
|
Dialogs.showError(scene, "Room not public", "Room is currently not public", null);
|
||||||
|
@ -166,12 +169,10 @@ public class Player {
|
||||||
// create threads, which read stdout and stderr of the player process. these are needed,
|
// 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
|
// because otherwise the internal buffer for these streams fill up and block the process
|
||||||
Thread std = new Thread(new StreamRedirector(playerProcess.getInputStream(), OutputStream.nullOutputStream()));
|
Thread std = new Thread(new StreamRedirector(playerProcess.getInputStream(), OutputStream.nullOutputStream()));
|
||||||
//Thread std = new Thread(new StreamRedirectThread(playerProcess.getInputStream(), System.out));
|
|
||||||
std.setName("Player stdout pipe");
|
std.setName("Player stdout pipe");
|
||||||
std.setDaemon(true);
|
std.setDaemon(true);
|
||||||
std.start();
|
std.start();
|
||||||
Thread err = new Thread(new StreamRedirector(playerProcess.getErrorStream(), OutputStream.nullOutputStream()));
|
Thread err = new Thread(new StreamRedirector(playerProcess.getErrorStream(), OutputStream.nullOutputStream()));
|
||||||
//Thread err = new Thread(new StreamRedirectThread(playerProcess.getErrorStream(), System.err));
|
|
||||||
err.setName("Player stderr pipe");
|
err.setName("Player stderr pipe");
|
||||||
err.setDaemon(true);
|
err.setDaemon(true);
|
||||||
err.start();
|
err.start();
|
||||||
|
|
|
@ -22,7 +22,7 @@ import javafx.scene.layout.StackPane;
|
||||||
import javafx.stage.Popup;
|
import javafx.stage.Popup;
|
||||||
|
|
||||||
public class PreviewPopupHandler implements EventHandler<MouseEvent> {
|
public class PreviewPopupHandler implements EventHandler<MouseEvent> {
|
||||||
private static final transient Logger LOG = LoggerFactory.getLogger(PreviewPopupHandler.class);
|
private static final Logger LOG = LoggerFactory.getLogger(PreviewPopupHandler.class);
|
||||||
|
|
||||||
private static final int offset = 10;
|
private static final int offset = 10;
|
||||||
private long timeForPopupOpen = TimeUnit.SECONDS.toMillis(1);
|
private long timeForPopupOpen = TimeUnit.SECONDS.toMillis(1);
|
||||||
|
@ -67,11 +67,11 @@ public class PreviewPopupHandler implements EventHandler<MouseEvent> {
|
||||||
} else if(event.getEventType() == MouseEvent.MOUSE_ENTERED) {
|
} else if(event.getEventType() == MouseEvent.MOUSE_ENTERED) {
|
||||||
popup.setX(event.getScreenX()+ offset);
|
popup.setX(event.getScreenX()+ offset);
|
||||||
popup.setY(event.getScreenY()+ offset);
|
popup.setY(event.getScreenY()+ offset);
|
||||||
JavaFxModel model = getModel(event);
|
JavaFxModel newModel = getModel(event);
|
||||||
if(model != null) {
|
if(newModel != null) {
|
||||||
closeCountdown = -1;
|
closeCountdown = -1;
|
||||||
boolean modelChanged = model != this.model;
|
boolean modelChanged = newModel != this.model;
|
||||||
this.model = model;
|
this.model = newModel;
|
||||||
if(popup.isShowing()) {
|
if(popup.isShowing()) {
|
||||||
openCountdown = -1;
|
openCountdown = -1;
|
||||||
if(modelChanged) {
|
if(modelChanged) {
|
||||||
|
@ -97,15 +97,15 @@ public class PreviewPopupHandler implements EventHandler<MouseEvent> {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
TableRow<JavaFxModel> row = (TableRow<JavaFxModel>) event.getSource();
|
TableRow<JavaFxModel> row = (TableRow<JavaFxModel>) event.getSource();
|
||||||
TableView<JavaFxModel> table = row.getTableView();
|
TableView<JavaFxModel> table = row.getTableView();
|
||||||
double offset = 0;
|
double columnOffset = 0;
|
||||||
double width = 0;
|
double width = 0;
|
||||||
for (TableColumn<JavaFxModel, ?> col : table.getColumns()) {
|
for (TableColumn<JavaFxModel, ?> col : table.getColumns()) {
|
||||||
offset += width;
|
columnOffset += width;
|
||||||
width = col.getWidth();
|
width = col.getWidth();
|
||||||
if(Objects.equals(col.getId(), "preview")) {
|
if(Objects.equals(col.getId(), "preview")) {
|
||||||
Point2D screenToLocal = table.screenToLocal(event.getScreenX(), event.getScreenY());
|
Point2D screenToLocal = table.screenToLocal(event.getScreenX(), event.getScreenY());
|
||||||
double x = screenToLocal.getX();
|
double x = screenToLocal.getX();
|
||||||
return x >= offset && x <= offset + width;
|
return x >= columnOffset && x <= columnOffset + width;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -176,6 +176,7 @@ public class PreviewPopupHandler implements EventHandler<MouseEvent> {
|
||||||
try {
|
try {
|
||||||
Thread.sleep(100);
|
Thread.sleep(100);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
LOG.error("PreviewPopupTimer interrupted");
|
LOG.error("PreviewPopupTimer interrupted");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
package ctbrec.ui;
|
||||||
|
|
||||||
|
public interface ShutdownListener {
|
||||||
|
void onShutdown();
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ import java.util.Optional;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import ctbrec.GlobalThreadPool;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.recorder.download.StreamSource;
|
import ctbrec.recorder.download.StreamSource;
|
||||||
import ctbrec.ui.controls.Dialogs;
|
import ctbrec.ui.controls.Dialogs;
|
||||||
|
@ -19,9 +20,10 @@ import javafx.stage.Stage;
|
||||||
public class StreamSourceSelectionDialog {
|
public class StreamSourceSelectionDialog {
|
||||||
private static final StreamSource BEST = new BestStreamSource();
|
private static final StreamSource BEST = new BestStreamSource();
|
||||||
|
|
||||||
private StreamSourceSelectionDialog() {}
|
private StreamSourceSelectionDialog() {
|
||||||
|
}
|
||||||
|
|
||||||
public static void show(Scene parent, Model model, Function<Model,Void> onSuccess, Function<Throwable, Void> onFail) {
|
public static void show(Scene parent, Model model, Function<Model, Void> onSuccess, Function<Throwable, Void> onFail) {
|
||||||
Task<List<StreamSource>> selectStreamSource = new Task<List<StreamSource>>() {
|
Task<List<StreamSource>> selectStreamSource = new Task<List<StreamSource>>() {
|
||||||
@Override
|
@Override
|
||||||
protected List<StreamSource> call() throws Exception {
|
protected List<StreamSource> call() throws Exception {
|
||||||
|
@ -35,7 +37,7 @@ public class StreamSourceSelectionDialog {
|
||||||
List<StreamSource> sources;
|
List<StreamSource> sources;
|
||||||
try {
|
try {
|
||||||
sources = selectStreamSource.get();
|
sources = selectStreamSource.get();
|
||||||
int selectedIndex = model.getStreamUrlIndex() > -1 ? Math.min(model.getStreamUrlIndex(), sources.size()-1) : sources.size()-1;
|
int selectedIndex = model.getStreamUrlIndex() > -1 ? Math.min(model.getStreamUrlIndex(), sources.size() - 1) : sources.size() - 1;
|
||||||
ChoiceDialog<StreamSource> choiceDialog = new ChoiceDialog<>(sources.get(selectedIndex), sources);
|
ChoiceDialog<StreamSource> choiceDialog = new ChoiceDialog<>(sources.get(selectedIndex), sources);
|
||||||
choiceDialog.setTitle("Stream Quality");
|
choiceDialog.setTitle("Stream Quality");
|
||||||
choiceDialog.setHeaderText("Select your preferred stream quality");
|
choiceDialog.setHeaderText("Select your preferred stream quality");
|
||||||
|
@ -45,7 +47,7 @@ public class StreamSourceSelectionDialog {
|
||||||
InputStream icon = Dialogs.class.getResourceAsStream("/icon.png");
|
InputStream icon = Dialogs.class.getResourceAsStream("/icon.png");
|
||||||
stage.getIcons().add(new Image(icon));
|
stage.getIcons().add(new Image(icon));
|
||||||
Optional<StreamSource> selectedSource = choiceDialog.showAndWait();
|
Optional<StreamSource> selectedSource = choiceDialog.showAndWait();
|
||||||
if(selectedSource.isPresent()) {
|
if (selectedSource.isPresent()) {
|
||||||
int index = -1;
|
int index = -1;
|
||||||
if (selectedSource.get() != BEST) {
|
if (selectedSource.get() != BEST) {
|
||||||
index = sources.indexOf(selectedSource.get());
|
index = sources.indexOf(selectedSource.get());
|
||||||
|
@ -61,7 +63,7 @@ public class StreamSourceSelectionDialog {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
selectStreamSource.setOnFailed(e -> onFail.apply(selectStreamSource.getException()));
|
selectStreamSource.setOnFailed(e -> onFail.apply(selectStreamSource.getException()));
|
||||||
new Thread(selectStreamSource).start();
|
GlobalThreadPool.submit(selectStreamSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class BestStreamSource extends StreamSource {
|
private static class BestStreamSource extends StreamSource {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import java.util.concurrent.ExecutionException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import ctbrec.Model;
|
import ctbrec.GlobalThreadPool;
|
||||||
import ctbrec.sites.Site;
|
import ctbrec.sites.Site;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
|
@ -19,11 +19,11 @@ import javafx.stage.Stage;
|
||||||
|
|
||||||
public class TipDialog extends TextInputDialog {
|
public class TipDialog extends TextInputDialog {
|
||||||
|
|
||||||
private static final transient Logger LOG = LoggerFactory.getLogger(TipDialog.class);
|
private static final Logger LOG = LoggerFactory.getLogger(TipDialog.class);
|
||||||
private Site site;
|
private Site site;
|
||||||
private Scene parent;
|
private Scene parent;
|
||||||
|
|
||||||
public TipDialog(Scene parent, Site site, Model model) {
|
public TipDialog(Scene parent, Site site) {
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.site = site;
|
this.site = site;
|
||||||
setTitle("Send Tip");
|
setTitle("Send Tip");
|
||||||
|
@ -32,7 +32,7 @@ public class TipDialog extends TextInputDialog {
|
||||||
setContentText("Amount of tokens to tip:");
|
setContentText("Amount of tokens to tip:");
|
||||||
setResizable(true);
|
setResizable(true);
|
||||||
getEditor().setDisable(true);
|
getEditor().setDisable(true);
|
||||||
if(parent != null) {
|
if (parent != null) {
|
||||||
Stage stage = (Stage) getDialogPane().getScene().getWindow();
|
Stage stage = (Stage) getDialogPane().getScene().getWindow();
|
||||||
stage.getScene().getStylesheets().addAll(parent.getStylesheets());
|
stage.getScene().getStylesheets().addAll(parent.getStylesheets());
|
||||||
}
|
}
|
||||||
|
@ -56,14 +56,14 @@ public class TipDialog extends TextInputDialog {
|
||||||
double tokens = get();
|
double tokens = get();
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
if (tokens <= 0) {
|
if (tokens <= 0) {
|
||||||
String msg = "Do you want to buy tokens now?\n\nIf you agree, "+site.getName()+" will open in a browser. "
|
String msg = "Do you want to buy tokens now?\n\nIf you agree, " + site.getName() + " will open in a browser. "
|
||||||
+ "The used address is an affiliate link, which supports me, but doesn't cost you anything more.";
|
+ "The used address is an affiliate link, which supports me, but doesn't cost you anything more.";
|
||||||
Alert buyTokens = new AutosizeAlert(Alert.AlertType.CONFIRMATION, msg, parent, ButtonType.NO, ButtonType.YES);
|
Alert buyTokens = new AutosizeAlert(Alert.AlertType.CONFIRMATION, msg, parent, ButtonType.NO, ButtonType.YES);
|
||||||
buyTokens.setTitle("No tokens");
|
buyTokens.setTitle("No tokens");
|
||||||
buyTokens.setHeaderText("You don't have any tokens");
|
buyTokens.setHeaderText("You don't have any tokens");
|
||||||
buyTokens.showAndWait();
|
buyTokens.showAndWait();
|
||||||
TipDialog.this.close();
|
TipDialog.this.close();
|
||||||
if(buyTokens.getResult() == ButtonType.YES) {
|
if (buyTokens.getResult() == ButtonType.YES) {
|
||||||
DesktopIntegration.open(site.getAffiliateLink());
|
DesktopIntegration.open(site.getAffiliateLink());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -72,13 +72,20 @@ public class TipDialog extends TextInputDialog {
|
||||||
setHeaderText("Current token balance: " + df.format(tokens));
|
setHeaderText("Current token balance: " + df.format(tokens));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (InterruptedException | ExecutionException e) {
|
} catch (InterruptedException e) {
|
||||||
LOG.error("Couldn't retrieve account balance", e);
|
Thread.currentThread().interrupt();
|
||||||
showErrorDialog(e);
|
handleExcetion(e);
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
handleExcetion(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
new Thread(task).start();
|
GlobalThreadPool.submit(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleExcetion(Exception e) {
|
||||||
|
LOG.error("Couldn't retrieve account balance", e);
|
||||||
|
showErrorDialog(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showErrorDialog(Throwable throwable) {
|
private void showErrorDialog(Throwable throwable) {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.google.common.eventbus.Subscribe;
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
|
||||||
|
import ctbrec.GlobalThreadPool;
|
||||||
import ctbrec.event.EventBusHolder;
|
import ctbrec.event.EventBusHolder;
|
||||||
import ctbrec.sites.Site;
|
import ctbrec.sites.Site;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
@ -19,7 +20,7 @@ import javafx.scene.control.Tooltip;
|
||||||
|
|
||||||
public class TokenLabel extends Label {
|
public class TokenLabel extends Label {
|
||||||
|
|
||||||
private static final transient Logger LOG = LoggerFactory.getLogger(TokenLabel.class);
|
private static final Logger LOG = LoggerFactory.getLogger(TokenLabel.class);
|
||||||
private double tokens = -1;
|
private double tokens = -1;
|
||||||
private Site site;
|
private Site site;
|
||||||
|
|
||||||
|
@ -72,17 +73,24 @@ public class TokenLabel extends Label {
|
||||||
@Override
|
@Override
|
||||||
protected void done() {
|
protected void done() {
|
||||||
try {
|
try {
|
||||||
double tokens = get();
|
tokens = get();
|
||||||
update(tokens);
|
update(tokens);
|
||||||
} catch (InterruptedException | ExecutionException e) {
|
} catch (InterruptedException e) {
|
||||||
LOG.error("Couldn't retrieve account balance", e);
|
Thread.currentThread().interrupt();
|
||||||
Platform.runLater(() -> {
|
handleException(e);
|
||||||
setText("Tokens: error");
|
} catch (ExecutionException e) {
|
||||||
setTooltip(new Tooltip(e.getMessage()));
|
handleException(e);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleException(Exception e) {
|
||||||
|
LOG.error("Couldn't retrieve account balance", e);
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
setText("Tokens: error");
|
||||||
|
setTooltip(new Tooltip(e.getMessage()));
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
new Thread(task).start();
|
GlobalThreadPool.submit(task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,13 @@ package ctbrec.ui.action;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import ctbrec.GlobalThreadPool;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.recorder.Recorder;
|
import ctbrec.recorder.Recorder;
|
||||||
import ctbrec.ui.controls.Dialogs;
|
import ctbrec.ui.controls.Dialogs;
|
||||||
|
@ -32,7 +32,7 @@ public class CheckModelAccountAction {
|
||||||
public void execute(Predicate<Model> filter) {
|
public void execute(Predicate<Model> filter) {
|
||||||
String buttonText = b.getText();
|
String buttonText = b.getText();
|
||||||
b.setDisable(true);
|
b.setDisable(true);
|
||||||
CompletableFuture.runAsync(() -> {
|
Runnable checker = (() -> {
|
||||||
List<Model> deletedAccounts = new ArrayList<>();
|
List<Model> deletedAccounts = new ArrayList<>();
|
||||||
try {
|
try {
|
||||||
List<Model> models = recorder.getModels().stream() //
|
List<Model> models = recorder.getModels().stream() //
|
||||||
|
@ -71,5 +71,6 @@ public class CheckModelAccountAction {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
GlobalThreadPool.submit(checker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import ctbrec.Config;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.ui.JavaFxModel;
|
import ctbrec.ui.JavaFxModel;
|
||||||
import ctbrec.ui.controls.Dialogs;
|
import ctbrec.ui.controls.Dialogs;
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.scene.Cursor;
|
import javafx.scene.Cursor;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.TableView;
|
import javafx.scene.control.TableView;
|
||||||
|
@ -30,23 +29,21 @@ public class EditNotesAction {
|
||||||
|
|
||||||
public void execute() {
|
public void execute() {
|
||||||
source.setCursor(Cursor.WAIT);
|
source.setCursor(Cursor.WAIT);
|
||||||
new Thread(() -> Platform.runLater(() -> {
|
String notes = Config.getInstance().getSettings().modelNotes.getOrDefault(model.getUrl(), "");
|
||||||
String notes = Config.getInstance().getSettings().modelNotes.getOrDefault(model.getUrl(), "");
|
Optional<String> newNotes = Dialogs.showTextInput(source.getScene(), "Model Notes", "Notes for " + model.getName(), notes);
|
||||||
Optional<String> newNotes = Dialogs.showTextInput(source.getScene(), "Model Notes", "Notes for " + model.getName(), notes);
|
newNotes.ifPresent(n -> {
|
||||||
newNotes.ifPresent(n -> {
|
if (!n.trim().isEmpty()) {
|
||||||
if (!n.trim().isEmpty()) {
|
Config.getInstance().getSettings().modelNotes.put(model.getUrl(), n);
|
||||||
Config.getInstance().getSettings().modelNotes.put(model.getUrl(), n);
|
} else {
|
||||||
} else {
|
Config.getInstance().getSettings().modelNotes.remove(model.getUrl());
|
||||||
Config.getInstance().getSettings().modelNotes.remove(model.getUrl());
|
}
|
||||||
}
|
try {
|
||||||
try {
|
Config.getInstance().save();
|
||||||
Config.getInstance().save();
|
} catch (IOException e) {
|
||||||
} catch (IOException e) {
|
LOG.warn("Couldn't save config", e);
|
||||||
LOG.warn("Couldn't save config", e);
|
}
|
||||||
}
|
});
|
||||||
});
|
table.refresh();
|
||||||
table.refresh();
|
source.setCursor(Cursor.DEFAULT);
|
||||||
source.setCursor(Cursor.DEFAULT);
|
|
||||||
})).start();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,9 @@ package ctbrec.ui.action;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.BlockingQueue;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import ctbrec.GlobalThreadPool;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.scene.Cursor;
|
import javafx.scene.Cursor;
|
||||||
|
@ -16,9 +12,6 @@ import javafx.scene.Node;
|
||||||
|
|
||||||
public class ModelMassEditAction {
|
public class ModelMassEditAction {
|
||||||
|
|
||||||
static BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
|
|
||||||
static ExecutorService threadPool = new ThreadPoolExecutor(2, 10, 10, TimeUnit.MINUTES, queue);
|
|
||||||
|
|
||||||
protected List<? extends Model> models;
|
protected List<? extends Model> models;
|
||||||
protected Consumer<Model> action;
|
protected Consumer<Model> action;
|
||||||
protected Node source;
|
protected Node source;
|
||||||
|
@ -42,7 +35,7 @@ public class ModelMassEditAction {
|
||||||
Consumer<Model> cb = Objects.requireNonNull(callback, "Callback is null, call execute() instead");
|
Consumer<Model> cb = Objects.requireNonNull(callback, "Callback is null, call execute() instead");
|
||||||
source.setCursor(Cursor.WAIT);
|
source.setCursor(Cursor.WAIT);
|
||||||
for (Model model : models) {
|
for (Model model : models) {
|
||||||
threadPool.submit(() -> {
|
GlobalThreadPool.submit(() -> {
|
||||||
action.accept(model);
|
action.accept(model);
|
||||||
cb.accept(model);
|
cb.accept(model);
|
||||||
Platform.runLater(() -> source.setCursor(Cursor.DEFAULT));
|
Platform.runLater(() -> source.setCursor(Cursor.DEFAULT));
|
||||||
|
|
|
@ -4,6 +4,7 @@ import java.io.File;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
|
import ctbrec.GlobalThreadPool;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.Settings.DirectoryStructure;
|
import ctbrec.Settings.DirectoryStructure;
|
||||||
import ctbrec.ui.DesktopIntegration;
|
import ctbrec.ui.DesktopIntegration;
|
||||||
|
@ -26,7 +27,7 @@ public class OpenRecordingsDir {
|
||||||
File fileForRecording = Config.getInstance().getFileForRecording(selectedModel, ".mp4", Instant.now());
|
File fileForRecording = Config.getInstance().getFileForRecording(selectedModel, ".mp4", Instant.now());
|
||||||
final File dir = getModelDirectory(fileForRecording);
|
final File dir = getModelDirectory(fileForRecording);
|
||||||
if (dir.exists()) {
|
if (dir.exists()) {
|
||||||
new Thread(() -> DesktopIntegration.open(dir)).start();
|
GlobalThreadPool.submit(() -> DesktopIntegration.open(dir));
|
||||||
} else {
|
} else {
|
||||||
Dialogs.showError(source.getScene(), "Directory does not exist", "There are no recordings for this model", null);
|
Dialogs.showError(source.getScene(), "Directory does not exist", "There are no recordings for this model", null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import java.security.NoSuchAlgorithmException;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import ctbrec.GlobalThreadPool;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.recorder.Recorder;
|
import ctbrec.recorder.Recorder;
|
||||||
import ctbrec.ui.controls.Dialogs;
|
import ctbrec.ui.controls.Dialogs;
|
||||||
|
@ -36,6 +37,6 @@ public class RemoveTimeLimitAction {
|
||||||
Dialogs.showError(source.getScene(), "Error", "Couln't remove stop date", e);
|
Dialogs.showError(source.getScene(), "Error", "Couln't remove stop date", e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}).whenComplete((r,e) -> source.setCursor(Cursor.DEFAULT));
|
}, GlobalThreadPool.get()).whenComplete((r,e) -> source.setCursor(Cursor.DEFAULT));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import java.util.concurrent.CompletableFuture;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import ctbrec.GlobalThreadPool;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.SubsequentAction;
|
import ctbrec.SubsequentAction;
|
||||||
import ctbrec.recorder.Recorder;
|
import ctbrec.recorder.Recorder;
|
||||||
|
@ -82,7 +83,7 @@ public class SetStopDateAction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}).whenComplete((r, e) -> {
|
}, GlobalThreadPool.get()).whenComplete((r, e) -> {
|
||||||
source.setCursor(Cursor.DEFAULT);
|
source.setCursor(Cursor.DEFAULT);
|
||||||
if (e != null) {
|
if (e != null) {
|
||||||
LOG.error("Error", e);
|
LOG.error("Error", e);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package ctbrec.ui.action;
|
package ctbrec.ui.action;
|
||||||
|
|
||||||
|
import ctbrec.GlobalThreadPool;
|
||||||
import ctbrec.recorder.Recorder;
|
import ctbrec.recorder.Recorder;
|
||||||
import ctbrec.ui.controls.Dialogs;
|
import ctbrec.ui.controls.Dialogs;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
@ -20,7 +21,7 @@ public class ToggleRecordingAction {
|
||||||
|
|
||||||
public void execute() {
|
public void execute() {
|
||||||
toggleButton.setCursor(Cursor.WAIT);
|
toggleButton.setCursor(Cursor.WAIT);
|
||||||
Thread t = new Thread(() -> {
|
GlobalThreadPool.submit(() -> {
|
||||||
try {
|
try {
|
||||||
if (pause) {
|
if (pause) {
|
||||||
recorder.pause();
|
recorder.pause();
|
||||||
|
@ -36,7 +37,5 @@ public class ToggleRecordingAction {
|
||||||
Platform.runLater(() -> toggleButton.setCursor(Cursor.DEFAULT));
|
Platform.runLater(() -> toggleButton.setCursor(Cursor.DEFAULT));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
t.setDaemon(true);
|
|
||||||
t.start();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,14 @@
|
||||||
*/
|
*/
|
||||||
package ctbrec.ui.controls;
|
package ctbrec.ui.controls;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import ctbrec.GlobalThreadPool;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.recorder.Recorder;
|
import ctbrec.recorder.Recorder;
|
||||||
import ctbrec.ui.action.PlayAction;
|
import ctbrec.ui.action.PlayAction;
|
||||||
|
@ -40,18 +48,15 @@ import javafx.event.EventHandler;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.scene.Cursor;
|
import javafx.scene.Cursor;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.ListCell;
|
||||||
|
import javafx.scene.control.ListView;
|
||||||
|
import javafx.scene.control.Skin;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.input.MouseEvent;
|
import javafx.scene.input.MouseEvent;
|
||||||
import javafx.scene.shape.Rectangle;
|
import javafx.scene.shape.Rectangle;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Popover page that displays a list of samples and sample categories for a given SampleCategory.
|
* Popover page that displays a list of samples and sample categories for a given SampleCategory.
|
||||||
|
@ -163,7 +168,7 @@ public class SearchPopoverTreeList extends PopoverTreeList<Model> implements Pop
|
||||||
follow = new Button("Follow");
|
follow = new Button("Follow");
|
||||||
follow.setOnAction(evt -> {
|
follow.setOnAction(evt -> {
|
||||||
setCursor(Cursor.WAIT);
|
setCursor(Cursor.WAIT);
|
||||||
CompletableFuture.runAsync(new Task<Boolean>() {
|
GlobalThreadPool.submit(new Task<Boolean>() {
|
||||||
@Override
|
@Override
|
||||||
protected Boolean call() throws Exception {
|
protected Boolean call() throws Exception {
|
||||||
model.getSite().login();
|
model.getSite().login();
|
||||||
|
@ -184,7 +189,7 @@ public class SearchPopoverTreeList extends PopoverTreeList<Model> implements Pop
|
||||||
record = new Button("Record");
|
record = new Button("Record");
|
||||||
record.setOnAction(evt -> {
|
record.setOnAction(evt -> {
|
||||||
setCursor(Cursor.WAIT);
|
setCursor(Cursor.WAIT);
|
||||||
CompletableFuture.runAsync(new Task<Void>() {
|
GlobalThreadPool.submit(new Task<Void>() {
|
||||||
@Override
|
@Override
|
||||||
protected Void call() throws Exception {
|
protected Void call() throws Exception {
|
||||||
recorder.addModel(model);
|
recorder.addModel(model);
|
||||||
|
@ -292,12 +297,12 @@ public class SearchPopoverTreeList extends PopoverTreeList<Model> implements Pop
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected double computePrefHeight(double width) {
|
protected double computePrefHeight(double width) {
|
||||||
return thumbSize + 20;
|
return thumbSize + 20.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected double computeMaxHeight(double width) {
|
protected double computeMaxHeight(double width) {
|
||||||
return thumbSize + 20;
|
return thumbSize + 20.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -12,6 +12,7 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
|
import ctbrec.GlobalThreadPool;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.io.HttpException;
|
import ctbrec.io.HttpException;
|
||||||
import ctbrec.recorder.download.StreamSource;
|
import ctbrec.recorder.download.StreamSource;
|
||||||
|
@ -79,7 +80,7 @@ public class StreamPreview extends StackPane {
|
||||||
double w = Config.getInstance().getSettings().thumbWidth;
|
double w = Config.getInstance().getSettings().thumbWidth;
|
||||||
double h = w / aspect;
|
double h = w / aspect;
|
||||||
resizeTo(w, h);
|
resizeTo(w, h);
|
||||||
} catch (Exception e) {}
|
} catch (Exception e) { /* nothing to do */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
if(future != null && !future.isDone()) {
|
if(future != null && !future.isDone()) {
|
||||||
|
@ -157,14 +158,14 @@ public class StreamPreview extends StackPane {
|
||||||
running = false;
|
running = false;
|
||||||
MediaPlayer old = videoPlayer;
|
MediaPlayer old = videoPlayer;
|
||||||
Future<?> oldFuture = future;
|
Future<?> oldFuture = future;
|
||||||
new Thread(() -> {
|
GlobalThreadPool.submit(() -> {
|
||||||
if(oldFuture != null && !oldFuture.isDone()) {
|
if(oldFuture != null && !oldFuture.isDone()) {
|
||||||
oldFuture.cancel(true);
|
oldFuture.cancel(true);
|
||||||
}
|
}
|
||||||
if(old != null) {
|
if(old != null) {
|
||||||
old.dispose();
|
old.dispose();
|
||||||
}
|
}
|
||||||
}).start();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onError(MediaPlayer videoPlayer) {
|
private void onError(MediaPlayer videoPlayer) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package ctbrec.ui.controls;
|
package ctbrec.ui.controls;
|
||||||
|
|
||||||
|
import ctbrec.GlobalThreadPool;
|
||||||
import javafx.animation.KeyFrame;
|
import javafx.animation.KeyFrame;
|
||||||
import javafx.animation.KeyValue;
|
import javafx.animation.KeyValue;
|
||||||
import javafx.animation.Timeline;
|
import javafx.animation.Timeline;
|
||||||
|
@ -13,6 +14,8 @@ import javafx.stage.StageStyle;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
|
|
||||||
public final class Toast {
|
public final class Toast {
|
||||||
|
private Toast() {}
|
||||||
|
|
||||||
public static void makeText(Scene owner, String toastMsg, int toastDelay, int fadeInDelay, int fadeOutDelay) {
|
public static void makeText(Scene owner, String toastMsg, int toastDelay, int fadeInDelay, int fadeOutDelay) {
|
||||||
Stage toastStage = new Stage();
|
Stage toastStage = new Stage();
|
||||||
toastStage.initOwner(owner.getWindow());
|
toastStage.initOwner(owner.getWindow());
|
||||||
|
@ -35,20 +38,18 @@ public final class Toast {
|
||||||
Timeline fadeInTimeline = new Timeline();
|
Timeline fadeInTimeline = new Timeline();
|
||||||
KeyFrame fadeInKey1 = new KeyFrame(Duration.millis(fadeInDelay), new KeyValue(toastStage.getScene().getRoot().opacityProperty(), 1));
|
KeyFrame fadeInKey1 = new KeyFrame(Duration.millis(fadeInDelay), new KeyValue(toastStage.getScene().getRoot().opacityProperty(), 1));
|
||||||
fadeInTimeline.getKeyFrames().add(fadeInKey1);
|
fadeInTimeline.getKeyFrames().add(fadeInKey1);
|
||||||
fadeInTimeline.setOnFinished((ae) -> {
|
fadeInTimeline.setOnFinished(ae -> GlobalThreadPool.submit(() -> {
|
||||||
new Thread(() -> {
|
try {
|
||||||
try {
|
Thread.sleep(toastDelay);
|
||||||
Thread.sleep(toastDelay);
|
} catch (InterruptedException e) {
|
||||||
} catch (InterruptedException e) {
|
Thread.currentThread().interrupt();
|
||||||
Thread.currentThread().interrupt();
|
}
|
||||||
}
|
Timeline fadeOutTimeline = new Timeline();
|
||||||
Timeline fadeOutTimeline = new Timeline();
|
KeyFrame fadeOutKey1 = new KeyFrame(Duration.millis(fadeOutDelay), new KeyValue(toastStage.getScene().getRoot().opacityProperty(), 0));
|
||||||
KeyFrame fadeOutKey1 = new KeyFrame(Duration.millis(fadeOutDelay), new KeyValue(toastStage.getScene().getRoot().opacityProperty(), 0));
|
fadeOutTimeline.getKeyFrames().add(fadeOutKey1);
|
||||||
fadeOutTimeline.getKeyFrames().add(fadeOutKey1);
|
fadeOutTimeline.setOnFinished(aeb -> toastStage.close());
|
||||||
fadeOutTimeline.setOnFinished((aeb) -> toastStage.close());
|
fadeOutTimeline.play();
|
||||||
fadeOutTimeline.play();
|
}));
|
||||||
}).start();
|
|
||||||
});
|
|
||||||
fadeInTimeline.play();
|
fadeInTimeline.play();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package ctbrec.ui.event;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import ctbrec.Model;
|
||||||
|
import ctbrec.ui.JavaFxModel;
|
||||||
|
|
||||||
|
public class PlayerStartedEvent {
|
||||||
|
|
||||||
|
private Model model;
|
||||||
|
private Instant timestamp;
|
||||||
|
|
||||||
|
public PlayerStartedEvent(Model model) {
|
||||||
|
this(model, Instant.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayerStartedEvent(Model model, Instant timestamp) {
|
||||||
|
this.model = unwrap(model);
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Model getModel() {
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj)
|
||||||
|
return true;
|
||||||
|
if (obj == null)
|
||||||
|
return false;
|
||||||
|
if (getClass() != obj.getClass())
|
||||||
|
return false;
|
||||||
|
PlayerStartedEvent other = (PlayerStartedEvent) obj;
|
||||||
|
return Objects.equals(timestamp, other.timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PlayerStartedEvent [model=" + model + ", timestamp=" + timestamp + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
private Model unwrap(Model model) {
|
||||||
|
if (model instanceof JavaFxModel) {
|
||||||
|
return ((JavaFxModel) model).getDelegate();
|
||||||
|
} else {
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,16 @@
|
||||||
package ctbrec.ui.news;
|
package ctbrec.ui.news;
|
||||||
|
|
||||||
|
import static ctbrec.io.HttpConstants.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import com.squareup.moshi.JsonAdapter;
|
import com.squareup.moshi.JsonAdapter;
|
||||||
import com.squareup.moshi.Moshi;
|
import com.squareup.moshi.Moshi;
|
||||||
|
|
||||||
|
import ctbrec.GlobalThreadPool;
|
||||||
import ctbrec.io.HttpException;
|
import ctbrec.io.HttpException;
|
||||||
import ctbrec.ui.CamrecApplication;
|
import ctbrec.ui.CamrecApplication;
|
||||||
import ctbrec.ui.controls.Dialogs;
|
import ctbrec.ui.controls.Dialogs;
|
||||||
|
@ -14,12 +23,6 @@ import javafx.scene.control.Tab;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import static ctbrec.io.HttpConstants.USER_AGENT;
|
|
||||||
|
|
||||||
public class NewsTab extends Tab implements TabSelectionListener {
|
public class NewsTab extends Tab implements TabSelectionListener {
|
||||||
private static final String ACCESS_TOKEN = "a2804d73a89951a22e0f8483a6fcec8943afd88b7ba17c459c095aa9e6f94fd0";
|
private static final String ACCESS_TOKEN = "a2804d73a89951a22e0f8483a6fcec8943afd88b7ba17c459c095aa9e6f94fd0";
|
||||||
|
@ -36,7 +39,7 @@ public class NewsTab extends Tab implements TabSelectionListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void selected() {
|
public void selected() {
|
||||||
new Thread(this::loadToots).start();
|
GlobalThreadPool.submit(this::loadToots);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadToots() {
|
private void loadToots() {
|
||||||
|
|
|
@ -9,13 +9,13 @@ import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
|
import ctbrec.GlobalThreadPool;
|
||||||
import ctbrec.Hmac;
|
import ctbrec.Hmac;
|
||||||
import ctbrec.Settings;
|
import ctbrec.Settings;
|
||||||
import ctbrec.Settings.DirectoryStructure;
|
import ctbrec.Settings.DirectoryStructure;
|
||||||
|
@ -129,6 +129,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
private SimpleBooleanProperty transportLayerSecurity;
|
private SimpleBooleanProperty transportLayerSecurity;
|
||||||
private SimpleBooleanProperty fastScrollSpeed;
|
private SimpleBooleanProperty fastScrollSpeed;
|
||||||
private SimpleBooleanProperty useHlsdl;
|
private SimpleBooleanProperty useHlsdl;
|
||||||
|
private SimpleBooleanProperty recentlyWatched;
|
||||||
private SimpleFileProperty hlsdlExecutable;
|
private SimpleFileProperty hlsdlExecutable;
|
||||||
private ExclusiveSelectionProperty recordLocal;
|
private ExclusiveSelectionProperty recordLocal;
|
||||||
private SimpleIntegerProperty postProcessingThreads;
|
private SimpleIntegerProperty postProcessingThreads;
|
||||||
|
@ -191,6 +192,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
confirmationDialogs = new SimpleBooleanProperty(null, "confirmationForDangerousActions", settings.confirmationForDangerousActions);
|
confirmationDialogs = new SimpleBooleanProperty(null, "confirmationForDangerousActions", settings.confirmationForDangerousActions);
|
||||||
useHlsdl = new SimpleBooleanProperty(null, "useHlsdl", settings.useHlsdl);
|
useHlsdl = new SimpleBooleanProperty(null, "useHlsdl", settings.useHlsdl);
|
||||||
hlsdlExecutable = new SimpleFileProperty(null, "hlsdlExecutable", settings.hlsdlExecutable);
|
hlsdlExecutable = new SimpleFileProperty(null, "hlsdlExecutable", settings.hlsdlExecutable);
|
||||||
|
recentlyWatched = new SimpleBooleanProperty(null, "recentlyWatched", settings.recentlyWatched);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createGui() {
|
private void createGui() {
|
||||||
|
@ -214,6 +216,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
Setting.of("Display stream resolution in overview", determineResolution),
|
Setting.of("Display stream resolution in overview", determineResolution),
|
||||||
Setting.of("Manually select stream quality", chooseStreamQuality, "Opens a dialog to select the video resolution before recording"),
|
Setting.of("Manually select stream quality", chooseStreamQuality, "Opens a dialog to select the video resolution before recording"),
|
||||||
Setting.of("Enable live previews (experimental)", livePreviews),
|
Setting.of("Enable live previews (experimental)", livePreviews),
|
||||||
|
Setting.of("Enable recently watched tab", recentlyWatched).needsRestart(),
|
||||||
Setting.of("Add models from clipboard", monitorClipboard, "Monitor clipboard for model URLs and automatically add them to the recorder").needsRestart(),
|
Setting.of("Add models from clipboard", monitorClipboard, "Monitor clipboard for model URLs and automatically add them to the recorder").needsRestart(),
|
||||||
Setting.of("Fast scroll speed", fastScrollSpeed, "Makes the thumbnail overviews scroll faster with the mouse wheel").needsRestart(),
|
Setting.of("Fast scroll speed", fastScrollSpeed, "Makes the thumbnail overviews scroll faster with the mouse wheel").needsRestart(),
|
||||||
Setting.of("Show confirmation dialogs", confirmationDialogs, "Show confirmation dialogs for irreversible actions"),
|
Setting.of("Show confirmation dialogs", confirmationDialogs, "Show confirmation dialogs for irreversible actions"),
|
||||||
|
@ -439,7 +442,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveConfig() {
|
public void saveConfig() {
|
||||||
CompletableFuture.runAsync(() -> {
|
GlobalThreadPool.submit(() -> {
|
||||||
try {
|
try {
|
||||||
Config.getInstance().save();
|
Config.getInstance().save();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|
|
@ -22,7 +22,7 @@ import okhttp3.HttpUrl;
|
||||||
|
|
||||||
public class BongaCamsElectronLoginDialog {
|
public class BongaCamsElectronLoginDialog {
|
||||||
|
|
||||||
private static final transient Logger LOG = LoggerFactory.getLogger(BongaCamsElectronLoginDialog.class);
|
private static final Logger LOG = LoggerFactory.getLogger(BongaCamsElectronLoginDialog.class);
|
||||||
public static final String DOMAIN = "bongacams.com";
|
public static final String DOMAIN = "bongacams.com";
|
||||||
public static final String URL = BongaCams.baseUrl + "/login";
|
public static final String URL = BongaCams.baseUrl + "/login";
|
||||||
private CookieJar cookieJar;
|
private CookieJar cookieJar;
|
||||||
|
@ -40,18 +40,18 @@ public class BongaCamsElectronLoginDialog {
|
||||||
msg.put("config", config);
|
msg.put("config", config);
|
||||||
browser.run(msg, msgHandler);
|
browser.run(msg, msgHandler);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
throw new IOException("Couldn't wait for login dialog", e);
|
throw new IOException("Couldn't wait for login dialog", e);
|
||||||
} finally {
|
} finally {
|
||||||
browser.close();
|
browser.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Consumer<String> msgHandler = (line) -> {
|
private Consumer<String> msgHandler = line -> {
|
||||||
if(!line.startsWith("{")) {
|
if(!line.startsWith("{")) {
|
||||||
System.err.println(line);
|
LOG.error("Didn't received a JSON object {}", line);
|
||||||
} else {
|
} else {
|
||||||
JSONObject json = new JSONObject(line);
|
JSONObject json = new JSONObject(line);
|
||||||
//LOG.debug("Browser: {}", json.toString(2));
|
|
||||||
if(json.has("url")) {
|
if(json.has("url")) {
|
||||||
String url = json.getString("url");
|
String url = json.getString("url");
|
||||||
if(url.endsWith("/login")) {
|
if(url.endsWith("/login")) {
|
||||||
|
@ -82,16 +82,16 @@ public class BongaCamsElectronLoginDialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(json.has("cookies")) {
|
if(json.has("cookies")) {
|
||||||
JSONArray _cookies = json.getJSONArray("cookies");
|
JSONArray cookiesFromBrowser = json.getJSONArray("cookies");
|
||||||
for (int i = 0; i < _cookies.length(); i++) {
|
for (int i = 0; i < cookiesFromBrowser.length(); i++) {
|
||||||
JSONObject cookie = _cookies.getJSONObject(i);
|
JSONObject cookie = cookiesFromBrowser.getJSONObject(i);
|
||||||
if(cookie.getString("domain").contains(DOMAIN)) {
|
if(cookie.getString("domain").contains(DOMAIN)) {
|
||||||
Builder b = new Cookie.Builder()
|
Builder b = new Cookie.Builder()
|
||||||
.path(cookie.getString("path"))
|
.path(cookie.getString("path"))
|
||||||
.domain(DOMAIN)
|
.domain(DOMAIN)
|
||||||
.name(cookie.getString("name"))
|
.name(cookie.getString("name"))
|
||||||
.value(cookie.getString("value"))
|
.value(cookie.getString("value"))
|
||||||
.expiresAt(Double.valueOf(cookie.optDouble("expirationDate")).longValue());
|
.expiresAt((long) cookie.optDouble("expirationDate"));
|
||||||
if(cookie.optBoolean("hostOnly")) {
|
if(cookie.optBoolean("hostOnly")) {
|
||||||
b.hostOnlyDomain(DOMAIN);
|
b.hostOnlyDomain(DOMAIN);
|
||||||
}
|
}
|
||||||
|
@ -108,8 +108,7 @@ public class BongaCamsElectronLoginDialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
URL _url = new URL(url);
|
if (Objects.equals(new URL(url).getPath(), "/")) {
|
||||||
if (Objects.equals(_url.getPath(), "/")) {
|
|
||||||
browser.close();
|
browser.close();
|
||||||
}
|
}
|
||||||
} catch (MalformedURLException e) {
|
} catch (MalformedURLException e) {
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
package ctbrec.ui.sites.bonga;
|
package ctbrec.ui.sites.bonga;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import ctbrec.sites.bonga.BongaCams;
|
import ctbrec.sites.bonga.BongaCams;
|
||||||
import ctbrec.sites.bonga.BongaCamsHttpClient;
|
import ctbrec.sites.bonga.BongaCamsHttpClient;
|
||||||
import ctbrec.ui.controls.Dialogs;
|
import ctbrec.ui.controls.Dialogs;
|
||||||
import ctbrec.ui.sites.AbstractSiteUi;
|
import ctbrec.ui.sites.AbstractSiteUi;
|
||||||
import ctbrec.ui.sites.ConfigUI;
|
import ctbrec.ui.sites.ConfigUI;
|
||||||
import ctbrec.ui.tabs.TabProvider;
|
import ctbrec.ui.tabs.TabProvider;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.concurrent.BlockingQueue;
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
|
||||||
|
|
||||||
public class BongaCamsSiteUi extends AbstractSiteUi {
|
public class BongaCamsSiteUi extends AbstractSiteUi {
|
||||||
|
|
||||||
|
@ -39,37 +38,20 @@ public class BongaCamsSiteUi extends AbstractSiteUi {
|
||||||
@Override
|
@Override
|
||||||
public synchronized boolean login() throws IOException {
|
public synchronized boolean login() throws IOException {
|
||||||
boolean automaticLogin = bongaCams.login();
|
boolean automaticLogin = bongaCams.login();
|
||||||
if(automaticLogin) {
|
if (automaticLogin) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
BlockingQueue<Boolean> queue = new LinkedBlockingQueue<>();
|
// login with external browser window
|
||||||
try {
|
try {
|
||||||
new Thread(() -> {
|
new BongaCamsElectronLoginDialog(bongaCams.getHttpClient().getCookieJar());
|
||||||
// login with external browser window
|
} catch (Exception e1) {
|
||||||
try {
|
LOG.error("Error logging in with external browser", e1);
|
||||||
new BongaCamsElectronLoginDialog(bongaCams.getHttpClient().getCookieJar());
|
Dialogs.showError("Login error", "Couldn't login to " + bongaCams.getName(), e1);
|
||||||
} catch (Exception e1) {
|
|
||||||
LOG.error("Error logging in with external browser", e1);
|
|
||||||
Dialogs.showError("Login error", "Couldn't login to " + bongaCams.getName(), e1);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
queue.put(true);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
LOG.error("Error while signaling termination", e);
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
queue.take();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
LOG.error("Error while waiting for login dialog to close", e);
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BongaCamsHttpClient httpClient = (BongaCamsHttpClient)bongaCams.getHttpClient();
|
BongaCamsHttpClient httpClient = (BongaCamsHttpClient) bongaCams.getHttpClient();
|
||||||
boolean loggedIn = httpClient.checkLoginSuccess();
|
boolean loggedIn = httpClient.checkLoginSuccess();
|
||||||
if(loggedIn) {
|
if (loggedIn) {
|
||||||
LOG.info("Logged in. User ID is {}", httpClient.getUserId());
|
LOG.info("Logged in. User ID is {}", httpClient.getUserId());
|
||||||
} else {
|
} else {
|
||||||
LOG.info("Login failed");
|
LOG.info("Login failed");
|
||||||
|
|
|
@ -22,7 +22,7 @@ import okhttp3.HttpUrl;
|
||||||
|
|
||||||
public class Cam4ElectronLoginDialog {
|
public class Cam4ElectronLoginDialog {
|
||||||
|
|
||||||
private static final transient Logger LOG = LoggerFactory.getLogger(Cam4ElectronLoginDialog.class);
|
private static final Logger LOG = LoggerFactory.getLogger(Cam4ElectronLoginDialog.class);
|
||||||
public static final String DOMAIN = "cam4.com";
|
public static final String DOMAIN = "cam4.com";
|
||||||
public static final String URL = Cam4.BASE_URI + "/login";
|
public static final String URL = Cam4.BASE_URI + "/login";
|
||||||
private CookieJar cookieJar;
|
private CookieJar cookieJar;
|
||||||
|
@ -40,15 +40,16 @@ public class Cam4ElectronLoginDialog {
|
||||||
msg.put("config", config);
|
msg.put("config", config);
|
||||||
browser.run(msg, msgHandler);
|
browser.run(msg, msgHandler);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
throw new IOException("Couldn't wait for login dialog", e);
|
throw new IOException("Couldn't wait for login dialog", e);
|
||||||
} finally {
|
} finally {
|
||||||
browser.close();
|
browser.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Consumer<String> msgHandler = (line) -> {
|
private Consumer<String> msgHandler = line -> {
|
||||||
if(!line.startsWith("{")) {
|
if(!line.startsWith("{")) {
|
||||||
System.err.println(line);
|
LOG.error("Didn't received a JSON object {}", line);
|
||||||
} else {
|
} else {
|
||||||
JSONObject json = new JSONObject(line);
|
JSONObject json = new JSONObject(line);
|
||||||
if(json.has("url")) {
|
if(json.has("url")) {
|
||||||
|
@ -75,11 +76,10 @@ public class Cam4ElectronLoginDialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(json.has("cookies")) {
|
if(json.has("cookies")) {
|
||||||
JSONArray _cookies = json.getJSONArray("cookies");
|
JSONArray cookiesFromBrowser = json.getJSONArray("cookies");
|
||||||
try {
|
try {
|
||||||
URL _url = new URL(url);
|
for (int i = 0; i < cookiesFromBrowser.length(); i++) {
|
||||||
for (int i = 0; i < _cookies.length(); i++) {
|
JSONObject cookie = cookiesFromBrowser.getJSONObject(i);
|
||||||
JSONObject cookie = _cookies.getJSONObject(i);
|
|
||||||
if(cookie.getString("domain").contains("cam4")) {
|
if(cookie.getString("domain").contains("cam4")) {
|
||||||
String domain = cookie.getString("domain");
|
String domain = cookie.getString("domain");
|
||||||
if(domain.startsWith(".")) {
|
if(domain.startsWith(".")) {
|
||||||
|
@ -91,12 +91,8 @@ public class Cam4ElectronLoginDialog {
|
||||||
cookieJar.saveFromResponse(HttpUrl.parse(Cam4.BASE_URI), Collections.singletonList(c));
|
cookieJar.saveFromResponse(HttpUrl.parse(Cam4.BASE_URI), Collections.singletonList(c));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Objects.equals(_url.getPath(), "/")) {
|
if (Objects.equals(new URL(url).getPath(), "/")) {
|
||||||
try {
|
closeBrowser();
|
||||||
browser.close();
|
|
||||||
} catch(IOException e) {
|
|
||||||
LOG.error("Couldn't send close request to browser", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (MalformedURLException e) {
|
} catch (MalformedURLException e) {
|
||||||
LOG.error("Couldn't parse new url {}", url, e);
|
LOG.error("Couldn't parse new url {}", url, e);
|
||||||
|
@ -112,7 +108,7 @@ public class Cam4ElectronLoginDialog {
|
||||||
.domain(domain)
|
.domain(domain)
|
||||||
.name(cookie.getString("name"))
|
.name(cookie.getString("name"))
|
||||||
.value(cookie.getString("value"))
|
.value(cookie.getString("value"))
|
||||||
.expiresAt(Double.valueOf(cookie.optDouble("expirationDate")).longValue());
|
.expiresAt((long) cookie.optDouble("expirationDate"));
|
||||||
if(cookie.optBoolean("hostOnly")) {
|
if(cookie.optBoolean("hostOnly")) {
|
||||||
b.hostOnlyDomain(domain);
|
b.hostOnlyDomain(domain);
|
||||||
}
|
}
|
||||||
|
@ -124,4 +120,12 @@ public class Cam4ElectronLoginDialog {
|
||||||
}
|
}
|
||||||
return b.build();
|
return b.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void closeBrowser() {
|
||||||
|
try {
|
||||||
|
browser.close();
|
||||||
|
} catch(IOException e) {
|
||||||
|
LOG.error("Couldn't send close request to browser", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package ctbrec.ui.sites.cam4;
|
package ctbrec.ui.sites.cam4;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.BlockingQueue;
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -13,10 +11,9 @@ import ctbrec.ui.controls.Dialogs;
|
||||||
import ctbrec.ui.sites.AbstractSiteUi;
|
import ctbrec.ui.sites.AbstractSiteUi;
|
||||||
import ctbrec.ui.sites.ConfigUI;
|
import ctbrec.ui.sites.ConfigUI;
|
||||||
import ctbrec.ui.tabs.TabProvider;
|
import ctbrec.ui.tabs.TabProvider;
|
||||||
import javafx.application.Platform;
|
|
||||||
|
|
||||||
public class Cam4SiteUi extends AbstractSiteUi {
|
public class Cam4SiteUi extends AbstractSiteUi {
|
||||||
private static final transient Logger LOG = LoggerFactory.getLogger(Cam4SiteUi.class);
|
private static final Logger LOG = LoggerFactory.getLogger(Cam4SiteUi.class);
|
||||||
|
|
||||||
private Cam4TabProvider tabProvider;
|
private Cam4TabProvider tabProvider;
|
||||||
private Cam4ConfigUI configUI;
|
private Cam4ConfigUI configUI;
|
||||||
|
@ -44,33 +41,13 @@ public class Cam4SiteUi extends AbstractSiteUi {
|
||||||
if (automaticLogin) {
|
if (automaticLogin) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
// login with external browser
|
||||||
BlockingQueue<Boolean> queue = new LinkedBlockingQueue<>();
|
|
||||||
|
|
||||||
Runnable showDialog = () -> {
|
|
||||||
// login with external browser
|
|
||||||
try {
|
|
||||||
new Cam4ElectronLoginDialog(cam4.getHttpClient().getCookieJar());
|
|
||||||
} catch (Exception e1) {
|
|
||||||
LOG.error("Error logging in with external browser", e1);
|
|
||||||
Dialogs.showError("Login error", "Couldn't login to " + cam4.getName(), e1);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
queue.put(true);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
LOG.error("Error while signaling termination", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Platform.runLater(showDialog);
|
|
||||||
try {
|
try {
|
||||||
queue.take();
|
new Cam4ElectronLoginDialog(cam4.getHttpClient().getCookieJar());
|
||||||
} catch (InterruptedException e) {
|
} catch (Exception e1) {
|
||||||
LOG.error("Error while waiting for login dialog to close", e);
|
LOG.error("Error logging in with external browser", e1);
|
||||||
throw new IOException(e);
|
Dialogs.showError("Login error", "Couldn't login to " + cam4.getName(), e1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Cam4HttpClient httpClient = (Cam4HttpClient) cam4.getHttpClient();
|
Cam4HttpClient httpClient = (Cam4HttpClient) cam4.getHttpClient();
|
||||||
boolean loggedIn = httpClient.checkLoginSuccess();
|
boolean loggedIn = httpClient.checkLoginSuccess();
|
||||||
return loggedIn;
|
return loggedIn;
|
||||||
|
|
|
@ -10,7 +10,6 @@ import java.util.List;
|
||||||
import java.util.Locale;
|
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 org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.jsoup.nodes.Element;
|
import org.jsoup.nodes.Element;
|
||||||
|
@ -43,14 +42,11 @@ public class Cam4UpdateService extends PaginatedScheduledService {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.loginRequired = loginRequired;
|
this.loginRequired = loginRequired;
|
||||||
|
|
||||||
ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
|
ExecutorService executor = Executors.newSingleThreadExecutor(r -> {
|
||||||
@Override
|
Thread t = new Thread(r);
|
||||||
public Thread newThread(Runnable r) {
|
t.setDaemon(true);
|
||||||
Thread t = new Thread(r);
|
t.setName("ThumbOverviewTab UpdateService");
|
||||||
t.setDaemon(true);
|
return t;
|
||||||
t.setName("ThumbOverviewTab UpdateService");
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
setExecutor(executor);
|
setExecutor(executor);
|
||||||
}
|
}
|
||||||
|
@ -60,16 +56,16 @@ public class Cam4UpdateService extends PaginatedScheduledService {
|
||||||
return new Task<List<Model>>() {
|
return new Task<List<Model>>() {
|
||||||
@Override
|
@Override
|
||||||
public List<Model> call() throws IOException {
|
public List<Model> call() throws IOException {
|
||||||
if(loginRequired && StringUtil.isBlank(Config.getInstance().getSettings().cam4Username)) {
|
if (loginRequired && StringUtil.isBlank(Config.getInstance().getSettings().cam4Username)) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
} else {
|
} else {
|
||||||
String url = Cam4UpdateService.this.url + "&page=" + page;
|
String pageUrl = Cam4UpdateService.this.url + "&page=" + page;
|
||||||
LOG.debug("Fetching page {}", url);
|
LOG.debug("Fetching page {}", pageUrl);
|
||||||
if(loginRequired) {
|
if (loginRequired) {
|
||||||
SiteUiFactory.getUi(site).login();
|
SiteUiFactory.getUi(site).login();
|
||||||
}
|
}
|
||||||
Request request = new Request.Builder()
|
Request request = new Request.Builder()
|
||||||
.url(url)
|
.url(pageUrl)
|
||||||
.header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
|
.header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
|
||||||
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
||||||
.build();
|
.build();
|
||||||
|
@ -91,7 +87,7 @@ public class Cam4UpdateService extends PaginatedScheduledService {
|
||||||
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);
|
model.setOnlineState(ONLINE);
|
||||||
if(boxHtml.contains("In private show")) {
|
if (boxHtml.contains("In private show")) {
|
||||||
model.setOnlineState(PRIVATE);
|
model.setOnlineState(PRIVATE);
|
||||||
}
|
}
|
||||||
models.add(model);
|
models.add(model);
|
||||||
|
|
|
@ -1,32 +1,5 @@
|
||||||
package ctbrec.ui.sites.camsoda;
|
package ctbrec.ui.sites.camsoda;
|
||||||
|
|
||||||
import ctbrec.Model;
|
|
||||||
import ctbrec.recorder.Recorder;
|
|
||||||
import ctbrec.sites.camsoda.Camsoda;
|
|
||||||
import ctbrec.ui.AutosizeAlert;
|
|
||||||
import ctbrec.ui.DesktopIntegration;
|
|
||||||
import ctbrec.ui.SiteUiFactory;
|
|
||||||
import ctbrec.ui.tabs.TabSelectionListener;
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.beans.value.ChangeListener;
|
|
||||||
import javafx.concurrent.Task;
|
|
||||||
import javafx.geometry.Insets;
|
|
||||||
import javafx.scene.Cursor;
|
|
||||||
import javafx.scene.Node;
|
|
||||||
import javafx.scene.control.*;
|
|
||||||
import javafx.scene.image.Image;
|
|
||||||
import javafx.scene.image.ImageView;
|
|
||||||
import javafx.scene.layout.BorderPane;
|
|
||||||
import javafx.scene.layout.GridPane;
|
|
||||||
import javafx.scene.text.Font;
|
|
||||||
import javafx.scene.text.FontWeight;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.Response;
|
|
||||||
import org.json.JSONArray;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
@ -40,7 +13,42 @@ import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import ctbrec.GlobalThreadPool;
|
||||||
|
import ctbrec.Model;
|
||||||
|
import ctbrec.recorder.Recorder;
|
||||||
|
import ctbrec.sites.camsoda.Camsoda;
|
||||||
|
import ctbrec.ui.AutosizeAlert;
|
||||||
|
import ctbrec.ui.DesktopIntegration;
|
||||||
|
import ctbrec.ui.SiteUiFactory;
|
||||||
|
import ctbrec.ui.tabs.TabSelectionListener;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.value.ChangeListener;
|
||||||
|
import javafx.concurrent.Task;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.scene.Cursor;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.Alert;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.ProgressIndicator;
|
||||||
|
import javafx.scene.control.ScrollPane;
|
||||||
|
import javafx.scene.control.Tab;
|
||||||
|
import javafx.scene.control.TitledPane;
|
||||||
|
import javafx.scene.control.Tooltip;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.layout.BorderPane;
|
||||||
|
import javafx.scene.layout.GridPane;
|
||||||
|
import javafx.scene.text.Font;
|
||||||
|
import javafx.scene.text.FontWeight;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
public class CamsodaShowsTab extends Tab implements TabSelectionListener {
|
public class CamsodaShowsTab extends Tab implements TabSelectionListener {
|
||||||
|
|
||||||
|
@ -129,7 +137,7 @@ public class CamsodaShowsTab extends Tab implements TabSelectionListener {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
CompletableFuture.runAsync(task);
|
GlobalThreadPool.submit(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -173,17 +181,17 @@ public class CamsodaShowsTab extends Tab implements TabSelectionListener {
|
||||||
grid.add(createLabel(formatter.format(endTime), false), 1, 1);
|
grid.add(createLabel(formatter.format(endTime), false), 1, 1);
|
||||||
Button record = new Button("Record Model");
|
Button record = new Button("Record Model");
|
||||||
record.setTooltip(new Tooltip(record.getText()));
|
record.setTooltip(new Tooltip(record.getText()));
|
||||||
record.setOnAction((evt) -> record(model));
|
record.setOnAction(evt -> record(model));
|
||||||
grid.add(record, 1, 2);
|
grid.add(record, 1, 2);
|
||||||
GridPane.setMargin(record, new Insets(10));
|
GridPane.setMargin(record, new Insets(10));
|
||||||
Button follow = new Button("Follow");
|
Button follow = new Button("Follow");
|
||||||
follow.setTooltip(new Tooltip(follow.getText()));
|
follow.setTooltip(new Tooltip(follow.getText()));
|
||||||
follow.setOnAction((evt) -> follow(model));
|
follow.setOnAction(evt -> follow(model));
|
||||||
grid.add(follow, 1, 3);
|
grid.add(follow, 1, 3);
|
||||||
GridPane.setMargin(follow, new Insets(10));
|
GridPane.setMargin(follow, new Insets(10));
|
||||||
Button openInBrowser = new Button("Open in Browser");
|
Button openInBrowser = new Button("Open in Browser");
|
||||||
openInBrowser.setTooltip(new Tooltip(openInBrowser.getText()));
|
openInBrowser.setTooltip(new Tooltip(openInBrowser.getText()));
|
||||||
openInBrowser.setOnAction((evt) -> DesktopIntegration.open(model.getUrl()));
|
openInBrowser.setOnAction(evt -> DesktopIntegration.open(model.getUrl()));
|
||||||
grid.add(openInBrowser, 1, 4);
|
grid.add(openInBrowser, 1, 4);
|
||||||
GridPane.setMargin(openInBrowser, new Insets(10));
|
GridPane.setMargin(openInBrowser, new Insets(10));
|
||||||
root.setCenter(grid);
|
root.setCenter(grid);
|
||||||
|
@ -195,7 +203,7 @@ public class CamsodaShowsTab extends Tab implements TabSelectionListener {
|
||||||
|
|
||||||
private void follow(Model model) {
|
private void follow(Model model) {
|
||||||
setCursor(Cursor.WAIT);
|
setCursor(Cursor.WAIT);
|
||||||
CompletableFuture.runAsync(() -> {
|
GlobalThreadPool.submit(() -> {
|
||||||
try {
|
try {
|
||||||
SiteUiFactory.getUi(model.getSite()).login();
|
SiteUiFactory.getUi(model.getSite()).login();
|
||||||
model.follow();
|
model.follow();
|
||||||
|
@ -203,30 +211,26 @@ public class CamsodaShowsTab extends Tab implements TabSelectionListener {
|
||||||
LOG.error("Couldn't follow model {}", model, e);
|
LOG.error("Couldn't follow model {}", model, e);
|
||||||
showErrorDialog("Oh no!", "Couldn't follow model", e.getMessage());
|
showErrorDialog("Oh no!", "Couldn't follow model", e.getMessage());
|
||||||
} finally {
|
} finally {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> setCursor(Cursor.DEFAULT));
|
||||||
setCursor(Cursor.DEFAULT);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void record(Model model) {
|
private void record(Model model) {
|
||||||
setCursor(Cursor.WAIT);
|
setCursor(Cursor.WAIT);
|
||||||
CompletableFuture.runAsync(() -> {
|
GlobalThreadPool.submit(() -> {
|
||||||
try {
|
try {
|
||||||
recorder.addModel(model);
|
recorder.addModel(model);
|
||||||
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) {
|
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) {
|
||||||
showErrorDialog("Oh no!", "Couldn't add model to the recorder", "Recorder error: " + e.getMessage());
|
showErrorDialog("Oh no!", "Couldn't add model to the recorder", "Recorder error: " + e.getMessage());
|
||||||
} finally {
|
} finally {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> setCursor(Cursor.DEFAULT));
|
||||||
setCursor(Cursor.DEFAULT);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadImage(Model model, ImageView thumb) {
|
private void loadImage(Model model, ImageView thumb) {
|
||||||
CompletableFuture.runAsync(() -> {
|
GlobalThreadPool.submit(() -> {
|
||||||
try {
|
try {
|
||||||
String url = camsoda.getBaseUrl() + "/api/v1/user/" + model.getName();
|
String url = camsoda.getBaseUrl() + "/api/v1/user/" + model.getName();
|
||||||
Request detailRequest = new Request.Builder().url(url).build();
|
Request detailRequest = new Request.Builder().url(url).build();
|
||||||
|
|
|
@ -5,7 +5,6 @@ import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
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 org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -21,7 +20,7 @@ import okhttp3.Response;
|
||||||
|
|
||||||
public class ChaturbateUpdateService extends PaginatedScheduledService {
|
public class ChaturbateUpdateService extends PaginatedScheduledService {
|
||||||
|
|
||||||
private static final transient Logger LOG = LoggerFactory.getLogger(ChaturbateUpdateService.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ChaturbateUpdateService.class);
|
||||||
private String url;
|
private String url;
|
||||||
private boolean loginRequired;
|
private boolean loginRequired;
|
||||||
private Chaturbate chaturbate;
|
private Chaturbate chaturbate;
|
||||||
|
@ -31,14 +30,11 @@ public class ChaturbateUpdateService extends PaginatedScheduledService {
|
||||||
this.loginRequired = loginRequired;
|
this.loginRequired = loginRequired;
|
||||||
this.chaturbate = chaturbate;
|
this.chaturbate = chaturbate;
|
||||||
|
|
||||||
ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
|
ExecutorService executor = Executors.newSingleThreadExecutor(r -> {
|
||||||
@Override
|
Thread t = new Thread(r);
|
||||||
public Thread newThread(Runnable r) {
|
t.setDaemon(true);
|
||||||
Thread t = new Thread(r);
|
t.setName("ThumbOverviewTab UpdateService");
|
||||||
t.setDaemon(true);
|
return t;
|
||||||
t.setName("ThumbOverviewTab UpdateService");
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
setExecutor(executor);
|
setExecutor(executor);
|
||||||
}
|
}
|
||||||
|
@ -51,12 +47,12 @@ public class ChaturbateUpdateService extends PaginatedScheduledService {
|
||||||
if(loginRequired && !chaturbate.credentialsAvailable()) {
|
if(loginRequired && !chaturbate.credentialsAvailable()) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
} else {
|
} else {
|
||||||
String url = ChaturbateUpdateService.this.url + "?page="+page+"&keywords=&_=" + System.currentTimeMillis();
|
String pageUrl = ChaturbateUpdateService.this.url + "?page="+page+"&keywords=&_=" + System.currentTimeMillis();
|
||||||
LOG.debug("Fetching page {}", url);
|
LOG.debug("Fetching page {}", pageUrl);
|
||||||
if(loginRequired) {
|
if(loginRequired) {
|
||||||
SiteUiFactory.getUi(chaturbate).login();
|
SiteUiFactory.getUi(chaturbate).login();
|
||||||
}
|
}
|
||||||
Request request = new Request.Builder().url(url).build();
|
Request request = new Request.Builder().url(pageUrl).build();
|
||||||
Response response = chaturbate.getHttpClient().execute(request);
|
Response response = chaturbate.getHttpClient().execute(request);
|
||||||
if (response.isSuccessful()) {
|
if (response.isSuccessful()) {
|
||||||
List<Model> models = ChaturbateModelParser.parseModels(chaturbate, response.body().string());
|
List<Model> models = ChaturbateModelParser.parseModels(chaturbate, response.body().string());
|
||||||
|
|
|
@ -5,6 +5,7 @@ import java.io.IOException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import ctbrec.GlobalThreadPool;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.sites.fc2live.Fc2Live;
|
import ctbrec.sites.fc2live.Fc2Live;
|
||||||
import ctbrec.sites.fc2live.Fc2Model;
|
import ctbrec.sites.fc2live.Fc2Model;
|
||||||
|
@ -16,7 +17,7 @@ import ctbrec.ui.sites.ConfigUI;
|
||||||
import ctbrec.ui.tabs.TabProvider;
|
import ctbrec.ui.tabs.TabProvider;
|
||||||
|
|
||||||
public class Fc2LiveSiteUi extends AbstractSiteUi {
|
public class Fc2LiveSiteUi extends AbstractSiteUi {
|
||||||
private static final transient Logger LOG = LoggerFactory.getLogger(Fc2LiveSiteUi.class);
|
private static final Logger LOG = LoggerFactory.getLogger(Fc2LiveSiteUi.class);
|
||||||
private Fc2Live fc2live;
|
private Fc2Live fc2live;
|
||||||
private Fc2TabProvider tabProvider;
|
private Fc2TabProvider tabProvider;
|
||||||
private Fc2LiveConfigUI configUi;
|
private Fc2LiveConfigUI configUi;
|
||||||
|
@ -44,10 +45,10 @@ public class Fc2LiveSiteUi extends AbstractSiteUi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean play(Model model) {
|
public boolean play(Model model) {
|
||||||
new Thread(() -> {
|
GlobalThreadPool.submit(() -> {
|
||||||
Fc2Model m;
|
Fc2Model m;
|
||||||
if(model instanceof JavaFxModel) {
|
if (model instanceof JavaFxModel) {
|
||||||
m = (Fc2Model) ((JavaFxModel)model).getDelegate();
|
m = (Fc2Model) ((JavaFxModel) model).getDelegate();
|
||||||
} else {
|
} else {
|
||||||
m = (Fc2Model) model;
|
m = (Fc2Model) model;
|
||||||
}
|
}
|
||||||
|
@ -55,12 +56,20 @@ public class Fc2LiveSiteUi extends AbstractSiteUi {
|
||||||
m.openWebsocket();
|
m.openWebsocket();
|
||||||
LOG.debug("Starting player for {}", model);
|
LOG.debug("Starting player for {}", model);
|
||||||
Player.play(model, false);
|
Player.play(model, false);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
handleException(e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
handleException(e);
|
||||||
|
} finally {
|
||||||
m.closeWebsocket();
|
m.closeWebsocket();
|
||||||
} catch (InterruptedException | IOException e) {
|
|
||||||
LOG.error("Error playing the stream", e);
|
|
||||||
Dialogs.showError("Player", "Error playing the stream", e);
|
|
||||||
}
|
}
|
||||||
}).start();
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleException(Exception e) {
|
||||||
|
LOG.error("Error playing the stream", e);
|
||||||
|
Dialogs.showError("Player", "Error playing the stream", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package ctbrec.ui.sites.jasmin;
|
package ctbrec.ui.sites.jasmin;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.BlockingQueue;
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -17,7 +15,7 @@ import ctbrec.ui.tabs.TabProvider;
|
||||||
|
|
||||||
public class LiveJasminSiteUi extends AbstractSiteUi {
|
public class LiveJasminSiteUi extends AbstractSiteUi {
|
||||||
|
|
||||||
private static final transient Logger LOG = LoggerFactory.getLogger(LiveJasminSiteUi.class);
|
private static final Logger LOG = LoggerFactory.getLogger(LiveJasminSiteUi.class);
|
||||||
private LiveJasmin liveJasmin;
|
private LiveJasmin liveJasmin;
|
||||||
private LiveJasminTabProvider tabProvider;
|
private LiveJasminTabProvider tabProvider;
|
||||||
private LiveJasminConfigUi configUi;
|
private LiveJasminConfigUi configUi;
|
||||||
|
@ -44,43 +42,27 @@ public class LiveJasminSiteUi extends AbstractSiteUi {
|
||||||
// renew login every 30 min
|
// renew login every 30 min
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
boolean renew = false;
|
boolean renew = false;
|
||||||
if((now - lastLoginTime) > TimeUnit.MINUTES.toMillis(30)) {
|
if ((now - lastLoginTime) > TimeUnit.MINUTES.toMillis(30)) {
|
||||||
renew = true;
|
renew = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean automaticLogin = liveJasmin.login();
|
boolean automaticLogin = liveJasmin.login();
|
||||||
if(automaticLogin && !renew) {
|
if (automaticLogin && !renew) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
lastLoginTime = System.currentTimeMillis();
|
lastLoginTime = System.currentTimeMillis();
|
||||||
BlockingQueue<Boolean> queue = new LinkedBlockingQueue<>();
|
|
||||||
|
|
||||||
new Thread (() -> {
|
|
||||||
// login with external browser window
|
|
||||||
try {
|
|
||||||
new LiveJasminElectronLoginDialog(liveJasmin.getHttpClient().getCookieJar());
|
|
||||||
} catch (Exception e1) {
|
|
||||||
LOG.error("Error logging in with external browser", e1);
|
|
||||||
Dialogs.showError("Login error", "Couldn't login to " + liveJasmin.getName(), e1);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
queue.put(true);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
LOG.error("Error while signaling termination", e);
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
|
|
||||||
|
// login with external browser window
|
||||||
try {
|
try {
|
||||||
queue.take();
|
new LiveJasminElectronLoginDialog(liveJasmin.getHttpClient().getCookieJar());
|
||||||
} catch (InterruptedException e) {
|
} catch (Exception e1) {
|
||||||
LOG.error("Error while waiting for login dialog to close", e);
|
LOG.error("Error logging in with external browser", e1);
|
||||||
throw new IOException(e);
|
Dialogs.showError("Login error", "Couldn't login to " + liveJasmin.getName(), e1);
|
||||||
}
|
}
|
||||||
|
|
||||||
LiveJasminHttpClient httpClient = (LiveJasminHttpClient)liveJasmin.getHttpClient();
|
LiveJasminHttpClient httpClient = (LiveJasminHttpClient) liveJasmin.getHttpClient();
|
||||||
boolean loggedIn = httpClient.checkLoginSuccess();
|
boolean loggedIn = httpClient.checkLoginSuccess();
|
||||||
if(loggedIn) {
|
if (loggedIn) {
|
||||||
LOG.info("Logged in");
|
LOG.info("Logged in");
|
||||||
} else {
|
} else {
|
||||||
LOG.info("Login failed");
|
LOG.info("Login failed");
|
||||||
|
|
|
@ -26,19 +26,16 @@ public class MyFreeCamsTabProvider extends TabProvider {
|
||||||
List<Tab> tabs = new ArrayList<>();
|
List<Tab> tabs = new ArrayList<>();
|
||||||
|
|
||||||
PaginatedScheduledService updateService = new OnlineCamsUpdateService();
|
PaginatedScheduledService updateService = new OnlineCamsUpdateService();
|
||||||
ThumbOverviewTab online = new ThumbOverviewTab("Online", updateService, myFreeCams);
|
tabs.add(createTab("Online", updateService));
|
||||||
online.setRecorder(recorder);
|
|
||||||
tabs.add(online);
|
|
||||||
|
|
||||||
friends = new MyFreeCamsFriendsTab(myFreeCams);
|
friends = new MyFreeCamsFriendsTab(myFreeCams);
|
||||||
friends.setRecorder(recorder);
|
friends.setRecorder(recorder);
|
||||||
|
friends.setImageAspectRatio(9.0 / 16.0);
|
||||||
|
friends.preserveAspectRatioProperty().set(false);
|
||||||
tabs.add(friends);
|
tabs.add(friends);
|
||||||
|
|
||||||
updateService = new HDCamsUpdateService();
|
updateService = new HDCamsUpdateService();
|
||||||
ThumbOverviewTab hd = createTab("HD", updateService);
|
tabs.add(createTab("HD", updateService));
|
||||||
hd.setImageAspectRatio(9.0 / 16.0);
|
|
||||||
hd.preserveAspectRatioProperty().set(false);
|
|
||||||
tabs.add(hd);
|
|
||||||
|
|
||||||
updateService = new PopularModelService();
|
updateService = new PopularModelService();
|
||||||
tabs.add(createTab("Most Popular", updateService));
|
tabs.add(createTab("Most Popular", updateService));
|
||||||
|
@ -54,6 +51,8 @@ public class MyFreeCamsTabProvider extends TabProvider {
|
||||||
|
|
||||||
private ThumbOverviewTab createTab(String title, PaginatedScheduledService updateService) {
|
private ThumbOverviewTab createTab(String title, PaginatedScheduledService updateService) {
|
||||||
ThumbOverviewTab tab = new ThumbOverviewTab(title, updateService, myFreeCams);
|
ThumbOverviewTab tab = new ThumbOverviewTab(title, updateService, myFreeCams);
|
||||||
|
tab.setImageAspectRatio(9.0 / 16.0);
|
||||||
|
tab.preserveAspectRatioProperty().set(false);
|
||||||
tab.setRecorder(recorder);
|
tab.setRecorder(recorder);
|
||||||
return tab;
|
return tab;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ import com.iheartradio.m3u8.ParseException;
|
||||||
import com.iheartradio.m3u8.PlaylistException;
|
import com.iheartradio.m3u8.PlaylistException;
|
||||||
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
|
import ctbrec.GlobalThreadPool;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.Settings;
|
import ctbrec.Settings;
|
||||||
import ctbrec.StringUtil;
|
import ctbrec.StringUtil;
|
||||||
|
@ -353,7 +354,7 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener {
|
||||||
|
|
||||||
if (Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
|
if (Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
|
||||||
MenuItem debug = new MenuItem("debug");
|
MenuItem debug = new MenuItem("debug");
|
||||||
debug.setOnAction(e -> new Thread(() -> {
|
debug.setOnAction(e -> GlobalThreadPool.submit(() -> {
|
||||||
for (Model m : selectedModels) {
|
for (Model m : selectedModels) {
|
||||||
try {
|
try {
|
||||||
List<StreamSource> sources = m.getStreamSources();
|
List<StreamSource> sources = m.getStreamSources();
|
||||||
|
@ -365,7 +366,7 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener {
|
||||||
LOG.error("Couldn't get stream sources", e1);
|
LOG.error("Couldn't get stream sources", e1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).start());
|
}));
|
||||||
menu.getItems().add(debug);
|
menu.getItems().add(debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -464,7 +465,7 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener {
|
||||||
|
|
||||||
private String escape(Property<?> prop) {
|
private String escape(Property<?> prop) {
|
||||||
String value = prop.getValue() != null ? prop.getValue().toString() : "";
|
String value = prop.getValue() != null ? prop.getValue().toString() : "";
|
||||||
return "\"" + value.replaceAll("\"", "\"\"") + "\"";
|
return "\"" + value.replace("\"", "\"\"") + "\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showColumnSelection(ActionEvent evt) {
|
private void showColumnSelection(ActionEvent evt) {
|
||||||
|
@ -566,7 +567,7 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener {
|
||||||
if(!file.exists()) {
|
if(!file.exists()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String json = new String(Files.readAllBytes(file.toPath()), "utf-8");
|
String json = new String(Files.readAllBytes(file.toPath()), UTF_8);
|
||||||
JSONArray data = new JSONArray(json);
|
JSONArray data = new JSONArray(json);
|
||||||
for (int i = 0; i < data.length(); i++) {
|
for (int i = 0; i < data.length(); i++) {
|
||||||
try {
|
try {
|
||||||
|
@ -701,7 +702,6 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener {
|
||||||
setProperty(continent, Optional.ofNullable(st.getM()).map(ctbrec.sites.mfc.Model::getContinent));
|
setProperty(continent, Optional.ofNullable(st.getM()).map(ctbrec.sites.mfc.Model::getContinent));
|
||||||
setProperty(occupation, Optional.ofNullable(st.getU()).map(User::getOccupation));
|
setProperty(occupation, Optional.ofNullable(st.getU()).map(User::getOccupation));
|
||||||
int flags = Optional.ofNullable(st.getM()).map(ctbrec.sites.mfc.Model::getFlags).orElse(0);
|
int flags = Optional.ofNullable(st.getM()).map(ctbrec.sites.mfc.Model::getFlags).orElse(0);
|
||||||
//isHd.set((flags & 1024) == 1024);
|
|
||||||
isWebrtc.set((flags & 524288) == 524288);
|
isWebrtc.set((flags & 524288) == 524288);
|
||||||
isHd.set(Optional.ofNullable(st.getU()).map(User::getPhase).orElse("z").equalsIgnoreCase("a"));
|
isHd.set(Optional.ofNullable(st.getU()).map(User::getPhase).orElse("z").equalsIgnoreCase("a"));
|
||||||
flagsProperty.setValue(flags);
|
flagsProperty.setValue(flags);
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package ctbrec.ui.sites.showup;
|
package ctbrec.ui.sites.showup;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.BlockingQueue;
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -44,33 +42,17 @@ public class ShowupSiteUi extends AbstractSiteUi {
|
||||||
if (automaticLogin) {
|
if (automaticLogin) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
BlockingQueue<Boolean> queue = new LinkedBlockingQueue<>();
|
// login with external browser window
|
||||||
try {
|
try {
|
||||||
new Thread(() -> {
|
new ShowupElectronLoginDialog(site.getHttpClient().getCookieJar());
|
||||||
// login with external browser window
|
} catch (Exception e1) {
|
||||||
try {
|
LOG.error("Error logging in with external browser", e1);
|
||||||
new ShowupElectronLoginDialog(site.getHttpClient().getCookieJar());
|
Dialogs.showError("Login error", "Couldn't login to " + site.getName(), e1);
|
||||||
} catch (Exception e1) {
|
|
||||||
LOG.error("Error logging in with external browser", e1);
|
|
||||||
Dialogs.showError("Login error", "Couldn't login to " + site.getName(), e1);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
queue.put(true);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
LOG.error("Error while signaling termination", e);
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
queue.take();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ShowupHttpClient httpClient = (ShowupHttpClient)site.getHttpClient();
|
ShowupHttpClient httpClient = (ShowupHttpClient) site.getHttpClient();
|
||||||
boolean loggedIn = httpClient.checkLoginSuccess();
|
boolean loggedIn = httpClient.checkLoginSuccess();
|
||||||
if(loggedIn) {
|
if (loggedIn) {
|
||||||
LOG.info("Logged in");
|
LOG.info("Logged in");
|
||||||
} else {
|
} else {
|
||||||
LOG.info("Login failed");
|
LOG.info("Login failed");
|
||||||
|
@ -78,5 +60,4 @@ public class ShowupSiteUi extends AbstractSiteUi {
|
||||||
return loggedIn;
|
return loggedIn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package ctbrec.ui.sites.stripchat;
|
package ctbrec.ui.sites.stripchat;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.BlockingQueue;
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -13,7 +11,6 @@ import ctbrec.ui.controls.Dialogs;
|
||||||
import ctbrec.ui.sites.AbstractSiteUi;
|
import ctbrec.ui.sites.AbstractSiteUi;
|
||||||
import ctbrec.ui.sites.ConfigUI;
|
import ctbrec.ui.sites.ConfigUI;
|
||||||
import ctbrec.ui.tabs.TabProvider;
|
import ctbrec.ui.tabs.TabProvider;
|
||||||
import javafx.application.Platform;
|
|
||||||
|
|
||||||
public class StripchatSiteUi extends AbstractSiteUi {
|
public class StripchatSiteUi extends AbstractSiteUi {
|
||||||
|
|
||||||
|
@ -45,31 +42,12 @@ public class StripchatSiteUi extends AbstractSiteUi {
|
||||||
if (automaticLogin) {
|
if (automaticLogin) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
// login with external browser
|
||||||
BlockingQueue<Boolean> queue = new LinkedBlockingQueue<>();
|
|
||||||
|
|
||||||
Runnable showDialog = () -> {
|
|
||||||
// login with external browser
|
|
||||||
try {
|
|
||||||
new StripchatElectronLoginDialog(site.getHttpClient().getCookieJar());
|
|
||||||
} catch (Exception e1) {
|
|
||||||
LOG.error("Error logging in with external browser", e1);
|
|
||||||
Dialogs.showError("Login error", "Couldn't login to " + site.getName(), e1);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
queue.put(true);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
LOG.error("Error while signaling termination", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Platform.runLater(showDialog);
|
|
||||||
try {
|
try {
|
||||||
queue.take();
|
new StripchatElectronLoginDialog(site.getHttpClient().getCookieJar());
|
||||||
} catch (InterruptedException e) {
|
} catch (Exception e1) {
|
||||||
LOG.error("Error while waiting for login dialog to close", e);
|
LOG.error("Error logging in with external browser", e1);
|
||||||
throw new IOException(e);
|
Dialogs.showError("Login error", "Couldn't login to " + site.getName(), e1);
|
||||||
}
|
}
|
||||||
|
|
||||||
StripchatHttpClient httpClient = (StripchatHttpClient) site.getHttpClient();
|
StripchatHttpClient httpClient = (StripchatHttpClient) site.getHttpClient();
|
||||||
|
|
|
@ -0,0 +1,346 @@
|
||||||
|
package ctbrec.ui.tabs;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.*;
|
||||||
|
import static java.nio.file.StandardOpenOption.*;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
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.event.EventBusHolder;
|
||||||
|
import ctbrec.io.InstantJsonAdapter;
|
||||||
|
import ctbrec.io.ModelJsonAdapter;
|
||||||
|
import ctbrec.recorder.Recorder;
|
||||||
|
import ctbrec.sites.Site;
|
||||||
|
import ctbrec.ui.DesktopIntegration;
|
||||||
|
import ctbrec.ui.ShutdownListener;
|
||||||
|
import ctbrec.ui.action.FollowAction;
|
||||||
|
import ctbrec.ui.action.PlayAction;
|
||||||
|
import ctbrec.ui.action.StartRecordingAction;
|
||||||
|
import ctbrec.ui.controls.CustomMouseBehaviorContextMenu;
|
||||||
|
import ctbrec.ui.controls.DateTimeCellFactory;
|
||||||
|
import ctbrec.ui.controls.SearchBox;
|
||||||
|
import ctbrec.ui.event.PlayerStartedEvent;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.scene.control.ContextMenu;
|
||||||
|
import javafx.scene.control.MenuItem;
|
||||||
|
import javafx.scene.control.ScrollPane;
|
||||||
|
import javafx.scene.control.SelectionMode;
|
||||||
|
import javafx.scene.control.Tab;
|
||||||
|
import javafx.scene.control.TableCell;
|
||||||
|
import javafx.scene.control.TableColumn;
|
||||||
|
import javafx.scene.control.TableColumn.SortType;
|
||||||
|
import javafx.scene.control.TableView;
|
||||||
|
import javafx.scene.input.Clipboard;
|
||||||
|
import javafx.scene.input.ClipboardContent;
|
||||||
|
import javafx.scene.input.ContextMenuEvent;
|
||||||
|
import javafx.scene.input.KeyCode;
|
||||||
|
import javafx.scene.input.KeyEvent;
|
||||||
|
import javafx.scene.input.MouseButton;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
|
import javafx.scene.layout.BorderPane;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
|
import javafx.util.Callback;
|
||||||
|
|
||||||
|
public class RecentlyWatchedTab extends Tab implements ShutdownListener {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(RecentlyWatchedTab.class);
|
||||||
|
|
||||||
|
private ObservableList<PlayerStartedEvent> filteredModels = FXCollections.observableArrayList();
|
||||||
|
private ObservableList<PlayerStartedEvent> observableModels = FXCollections.observableArrayList();
|
||||||
|
private TableView<PlayerStartedEvent> table = new TableView<>();
|
||||||
|
private ContextMenu popup;
|
||||||
|
private ReentrantLock lock = new ReentrantLock();
|
||||||
|
private Recorder recorder;
|
||||||
|
private List<Site> sites;
|
||||||
|
|
||||||
|
public RecentlyWatchedTab(Recorder recorder, List<Site> sites) {
|
||||||
|
this.recorder = recorder;
|
||||||
|
this.sites = sites;
|
||||||
|
setText("Recently Watched");
|
||||||
|
createGui();
|
||||||
|
loadHistory();
|
||||||
|
subscribeToPlayerEvents();
|
||||||
|
setOnClosed(evt -> onShutdown());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createGui() {
|
||||||
|
BorderPane layout = new BorderPane();
|
||||||
|
layout.setPadding(new Insets(5, 10, 10, 10));
|
||||||
|
|
||||||
|
SearchBox filterInput = new SearchBox(false);
|
||||||
|
filterInput.setPromptText("Filter");
|
||||||
|
filterInput.textProperty().addListener( (observableValue, oldValue, newValue) -> {
|
||||||
|
String filter = filterInput.getText();
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
filter(filter);
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
filterInput.getStyleClass().remove("search-box-icon");
|
||||||
|
HBox.setHgrow(filterInput, Priority.ALWAYS);
|
||||||
|
HBox topBar = new HBox(5);
|
||||||
|
topBar.getChildren().addAll(filterInput);
|
||||||
|
layout.setTop(topBar);
|
||||||
|
BorderPane.setMargin(topBar, new Insets(0, 0, 5, 0));
|
||||||
|
|
||||||
|
table.setItems(observableModels);
|
||||||
|
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||||
|
table.addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, event -> {
|
||||||
|
popup = createContextMenu();
|
||||||
|
if (popup != null) {
|
||||||
|
popup.show(table, event.getScreenX(), event.getScreenY());
|
||||||
|
}
|
||||||
|
event.consume();
|
||||||
|
});
|
||||||
|
table.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
|
||||||
|
if (popup != null) {
|
||||||
|
popup.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
table.addEventHandler(KeyEvent.KEY_PRESSED, event -> {
|
||||||
|
List<PlayerStartedEvent> selectedModels = table.getSelectionModel().getSelectedItems();
|
||||||
|
if (event.getCode() == KeyCode.DELETE) {
|
||||||
|
delete(selectedModels);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
int idx = 0;
|
||||||
|
TableColumn<PlayerStartedEvent, String> name = createTableColumn("Model", 200, idx++);
|
||||||
|
name.setId("name");
|
||||||
|
name.setCellValueFactory(cdf -> new SimpleStringProperty(cdf.getValue().getModel().getDisplayName()));
|
||||||
|
name.setCellFactory(new ClickableCellFactory<>());
|
||||||
|
table.getColumns().add(name);
|
||||||
|
|
||||||
|
TableColumn<PlayerStartedEvent, String> url = createTableColumn("URL", 400, idx);
|
||||||
|
url.setCellValueFactory(cdf -> new SimpleStringProperty(cdf.getValue().getModel().getUrl()));
|
||||||
|
url.setCellFactory(new ClickableCellFactory<>());
|
||||||
|
url.setEditable(false);
|
||||||
|
url.setId("url");
|
||||||
|
table.getColumns().add(url);
|
||||||
|
|
||||||
|
TableColumn<PlayerStartedEvent, Instant> timestamp = createTableColumn("Timestamp", 150, idx);
|
||||||
|
timestamp.setId("timestamp");
|
||||||
|
timestamp.setCellValueFactory(cdf -> new SimpleObjectProperty<Instant>(cdf.getValue().getTimestamp()));
|
||||||
|
timestamp.setCellFactory(new DateTimeCellFactory<>());
|
||||||
|
timestamp.setEditable(false);
|
||||||
|
timestamp.setSortType(SortType.DESCENDING);
|
||||||
|
table.getColumns().add(timestamp);
|
||||||
|
table.getSortOrder().add(timestamp);
|
||||||
|
|
||||||
|
ScrollPane scrollPane = new ScrollPane();
|
||||||
|
scrollPane.setFitToHeight(true);
|
||||||
|
scrollPane.setFitToWidth(true);
|
||||||
|
scrollPane.setContent(table);
|
||||||
|
scrollPane.setStyle("-fx-background-color: -fx-background");
|
||||||
|
layout.setCenter(scrollPane);
|
||||||
|
setContent(layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> TableColumn<PlayerStartedEvent, T> createTableColumn(String text, int width, int idx) {
|
||||||
|
TableColumn<PlayerStartedEvent, T> tc = new TableColumn<>(text);
|
||||||
|
tc.setPrefWidth(width);
|
||||||
|
tc.setUserData(idx);
|
||||||
|
return tc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void filter(String filter) {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
if (StringUtil.isBlank(filter)) {
|
||||||
|
observableModels.addAll(filteredModels);
|
||||||
|
filteredModels.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] tokens = filter.split(" ");
|
||||||
|
observableModels.addAll(filteredModels);
|
||||||
|
filteredModels.clear();
|
||||||
|
for (int i = 0; i < table.getItems().size(); i++) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (TableColumn<PlayerStartedEvent, ?> tc : table.getColumns()) {
|
||||||
|
Object cellData = tc.getCellData(i);
|
||||||
|
if(cellData != null) {
|
||||||
|
String content = cellData.toString();
|
||||||
|
sb.append(content).append(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String searchText = sb.toString();
|
||||||
|
|
||||||
|
boolean tokensMissing = false;
|
||||||
|
for (String token : tokens) {
|
||||||
|
if(!searchText.toLowerCase().contains(token.toLowerCase())) {
|
||||||
|
tokensMissing = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(tokensMissing) {
|
||||||
|
PlayerStartedEvent sessionState = table.getItems().get(i);
|
||||||
|
filteredModels.add(sessionState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
observableModels.removeAll(filteredModels);
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ContextMenu createContextMenu() {
|
||||||
|
ObservableList<PlayerStartedEvent> selectedRows = table.getSelectionModel().getSelectedItems();
|
||||||
|
if (selectedRows.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Model> selectedModels = selectedRows.stream().map(PlayerStartedEvent::getModel).collect(Collectors.toList());
|
||||||
|
MenuItem copyUrl = new MenuItem("Copy URL");
|
||||||
|
copyUrl.setOnAction(e -> {
|
||||||
|
Model selected = selectedModels.get(0);
|
||||||
|
final Clipboard clipboard = Clipboard.getSystemClipboard();
|
||||||
|
final ClipboardContent content = new ClipboardContent();
|
||||||
|
content.putString(selected.getUrl());
|
||||||
|
clipboard.setContent(content);
|
||||||
|
});
|
||||||
|
|
||||||
|
MenuItem startRecording = new MenuItem("Start Recording");
|
||||||
|
startRecording.setOnAction(e -> startRecording(selectedModels));
|
||||||
|
MenuItem openInBrowser = new MenuItem("Open in Browser");
|
||||||
|
openInBrowser.setOnAction(e -> DesktopIntegration.open(selectedModels.get(0).getUrl()));
|
||||||
|
MenuItem openInPlayer = new MenuItem("Open in Player");
|
||||||
|
openInPlayer.setOnAction(e -> openInPlayer(selectedModels.get(0)));
|
||||||
|
MenuItem follow = new MenuItem("Follow");
|
||||||
|
follow.setOnAction(e -> new FollowAction(getTabPane(), selectedModels).execute());
|
||||||
|
MenuItem delete = new MenuItem("Delete");
|
||||||
|
delete.setOnAction(e -> delete(selectedRows));
|
||||||
|
|
||||||
|
MenuItem clearHistory = new MenuItem("Clear history");
|
||||||
|
clearHistory.setOnAction(e -> clearHistory());
|
||||||
|
|
||||||
|
ContextMenu menu = new CustomMouseBehaviorContextMenu();
|
||||||
|
menu.getItems().addAll(startRecording, copyUrl, openInPlayer, openInBrowser, follow, delete, clearHistory);
|
||||||
|
|
||||||
|
if (selectedModels.size() > 1) {
|
||||||
|
copyUrl.setDisable(true);
|
||||||
|
openInPlayer.setDisable(true);
|
||||||
|
openInBrowser.setDisable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearHistory() {
|
||||||
|
observableModels.clear();
|
||||||
|
filteredModels.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void delete(List<PlayerStartedEvent> selectedRows) {
|
||||||
|
observableModels.removeAll(selectedRows);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startRecording(List<Model> selectedModels) {
|
||||||
|
new StartRecordingAction(getTabPane(), selectedModels, recorder).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openInPlayer(Model selectedModel) {
|
||||||
|
new PlayAction(getTabPane(), selectedModel).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void subscribeToPlayerEvents() {
|
||||||
|
EventBusHolder.BUS.register(new Object() {
|
||||||
|
@Subscribe
|
||||||
|
public void handleEvent(PlayerStartedEvent evt) {
|
||||||
|
table.getItems().add(evt);
|
||||||
|
table.sort();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ClickableCellFactory<S, T> implements Callback<TableColumn<S, T>, TableCell<S, T>> {
|
||||||
|
@Override
|
||||||
|
public TableCell<S, T> call(TableColumn<S, T> param) {
|
||||||
|
TableCell<S, T> cell = new TableCell<>() {
|
||||||
|
@Override
|
||||||
|
protected void updateItem(Object item, boolean empty) {
|
||||||
|
setText(empty ? "" : Objects.toString(item));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
cell.addEventFilter(MouseEvent.MOUSE_CLICKED, event -> {
|
||||||
|
if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
|
||||||
|
Model selectedModel = table.getSelectionModel().getSelectedItem().getModel();
|
||||||
|
if(selectedModel != null) {
|
||||||
|
new PlayAction(table, selectedModel).execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveHistory() throws IOException {
|
||||||
|
Moshi moshi = new Moshi.Builder()
|
||||||
|
.add(Model.class, new ModelJsonAdapter(sites))
|
||||||
|
.add(Instant.class, new InstantJsonAdapter())
|
||||||
|
.build();
|
||||||
|
Type type = Types.newParameterizedType(List.class, PlayerStartedEvent.class);
|
||||||
|
JsonAdapter<List<PlayerStartedEvent>> adapter = moshi.adapter(type);
|
||||||
|
String json = adapter.indent(" ").toJson(observableModels);
|
||||||
|
File recentlyWatched = new File(Config.getInstance().getConfigDir(), "recently_watched.json");
|
||||||
|
LOG.debug("Saving recently watched models to {}", recentlyWatched.getAbsolutePath());
|
||||||
|
Files.createDirectories(recentlyWatched.getParentFile().toPath());
|
||||||
|
Files.write(recentlyWatched.toPath(), json.getBytes(UTF_8), CREATE, WRITE, TRUNCATE_EXISTING);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadHistory() {
|
||||||
|
File recentlyWatched = new File(Config.getInstance().getConfigDir(), "recently_watched.json");
|
||||||
|
if(!recentlyWatched.exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("Loading recently watched models from {}", recentlyWatched.getAbsolutePath());
|
||||||
|
Moshi moshi = new Moshi.Builder()
|
||||||
|
.add(Model.class, new ModelJsonAdapter(sites))
|
||||||
|
.add(Instant.class, new InstantJsonAdapter())
|
||||||
|
.build();
|
||||||
|
Type type = Types.newParameterizedType(List.class, PlayerStartedEvent.class);
|
||||||
|
JsonAdapter<List<PlayerStartedEvent>> adapter = moshi.adapter(type);
|
||||||
|
try {
|
||||||
|
List<PlayerStartedEvent> fromJson = adapter.fromJson(Files.readString(recentlyWatched.toPath(), UTF_8));
|
||||||
|
observableModels.addAll(fromJson);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Couldn't load recently watched models", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onShutdown() {
|
||||||
|
try {
|
||||||
|
saveHistory();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Couldn't safe recently watched models", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,12 +4,13 @@ import java.util.List;
|
||||||
|
|
||||||
import ctbrec.recorder.Recorder;
|
import ctbrec.recorder.Recorder;
|
||||||
import ctbrec.sites.Site;
|
import ctbrec.sites.Site;
|
||||||
|
import ctbrec.ui.ShutdownListener;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
import javafx.geometry.Side;
|
import javafx.geometry.Side;
|
||||||
import javafx.scene.control.Tab;
|
import javafx.scene.control.Tab;
|
||||||
import javafx.scene.control.TabPane;
|
import javafx.scene.control.TabPane;
|
||||||
|
|
||||||
public class RecordedTab extends Tab implements TabSelectionListener {
|
public class RecordedTab extends Tab implements TabSelectionListener, ShutdownListener {
|
||||||
|
|
||||||
private TabPane tabPane;
|
private TabPane tabPane;
|
||||||
private RecordedModelsTab recordedModelsTab;
|
private RecordedModelsTab recordedModelsTab;
|
||||||
|
@ -54,7 +55,8 @@ public class RecordedTab extends Tab implements TabSelectionListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveState() {
|
@Override
|
||||||
|
public void onShutdown() {
|
||||||
recordedModelsTab.saveState();
|
recordedModelsTab.saveState();
|
||||||
recordLaterTab.saveState();
|
recordLaterTab.saveState();
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
|
import ctbrec.GlobalThreadPool;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.Recording;
|
import ctbrec.Recording;
|
||||||
import ctbrec.Recording.State;
|
import ctbrec.Recording.State;
|
||||||
|
@ -45,6 +46,7 @@ import ctbrec.ui.DesktopIntegration;
|
||||||
import ctbrec.ui.FileDownload;
|
import ctbrec.ui.FileDownload;
|
||||||
import ctbrec.ui.JavaFxRecording;
|
import ctbrec.ui.JavaFxRecording;
|
||||||
import ctbrec.ui.Player;
|
import ctbrec.ui.Player;
|
||||||
|
import ctbrec.ui.ShutdownListener;
|
||||||
import ctbrec.ui.action.FollowAction;
|
import ctbrec.ui.action.FollowAction;
|
||||||
import ctbrec.ui.action.PauseAction;
|
import ctbrec.ui.action.PauseAction;
|
||||||
import ctbrec.ui.action.PlayAction;
|
import ctbrec.ui.action.PlayAction;
|
||||||
|
@ -90,7 +92,7 @@ import javafx.scene.text.Font;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
|
|
||||||
public class RecordingsTab extends Tab implements TabSelectionListener {
|
public class RecordingsTab extends Tab implements TabSelectionListener, ShutdownListener {
|
||||||
private static final String ERROR_WHILE_DOWNLOADING_RECORDING = "Error while downloading recording";
|
private static final String ERROR_WHILE_DOWNLOADING_RECORDING = "Error while downloading recording";
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(RecordingsTab.class);
|
private static final Logger LOG = LoggerFactory.getLogger(RecordingsTab.class);
|
||||||
|
@ -496,9 +498,9 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
||||||
|
|
||||||
private void openContactSheet(JavaFxRecording recording) {
|
private void openContactSheet(JavaFxRecording recording) {
|
||||||
if (config.getSettings().localRecording) {
|
if (config.getSettings().localRecording) {
|
||||||
recording.getContactSheet().ifPresent(f -> new Thread(() -> DesktopIntegration.open(f)).start());
|
recording.getContactSheet().ifPresent(f -> GlobalThreadPool.submit(() -> DesktopIntegration.open(f)));
|
||||||
} else {
|
} else {
|
||||||
recording.getContactSheet().ifPresent(f -> new Thread(() -> {
|
recording.getContactSheet().ifPresent(f -> GlobalThreadPool.submit(() -> {
|
||||||
File target;
|
File target;
|
||||||
try {
|
try {
|
||||||
target = File.createTempFile("cs_", ".jpg");
|
target = File.createTempFile("cs_", ".jpg");
|
||||||
|
@ -516,7 +518,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
||||||
} catch (IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e) {
|
} catch (IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e) {
|
||||||
Dialogs.showError(getTabPane().getScene(), "Download Error", "An error occurred while downloading the contact sheet", e);
|
Dialogs.showError(getTabPane().getScene(), "Download Error", "An error occurred while downloading the contact sheet", e);
|
||||||
}
|
}
|
||||||
}).start());
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -526,7 +528,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
||||||
Optional<String> newNote = Dialogs.showTextInput(source.getScene(), "Recording Notes", "", notes);
|
Optional<String> newNote = Dialogs.showTextInput(source.getScene(), "Recording Notes", "", notes);
|
||||||
if (newNote.isPresent()) {
|
if (newNote.isPresent()) {
|
||||||
table.setCursor(Cursor.WAIT);
|
table.setCursor(Cursor.WAIT);
|
||||||
Thread backgroundThread = new Thread(() -> {
|
GlobalThreadPool.submit(() -> {
|
||||||
List<Exception> exceptions = new ArrayList<>();
|
List<Exception> exceptions = new ArrayList<>();
|
||||||
try {
|
try {
|
||||||
recording.setNote(newNote.get());
|
recording.setNote(newNote.get());
|
||||||
|
@ -542,13 +544,12 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
backgroundThread.start();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pin(List<JavaFxRecording> recordings) {
|
private void pin(List<JavaFxRecording> recordings) {
|
||||||
table.setCursor(Cursor.WAIT);
|
table.setCursor(Cursor.WAIT);
|
||||||
Thread backgroundThread = new Thread(() -> {
|
GlobalThreadPool.submit(() -> {
|
||||||
List<Exception> exceptions = new ArrayList<>();
|
List<Exception> exceptions = new ArrayList<>();
|
||||||
try {
|
try {
|
||||||
for (JavaFxRecording javaFxRecording : recordings) {
|
for (JavaFxRecording javaFxRecording : recordings) {
|
||||||
|
@ -569,12 +570,11 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
backgroundThread.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void unpin(List<JavaFxRecording> recordings) {
|
private void unpin(List<JavaFxRecording> recordings) {
|
||||||
table.setCursor(Cursor.WAIT);
|
table.setCursor(Cursor.WAIT);
|
||||||
Thread backgroundThread = new Thread(() -> {
|
GlobalThreadPool.submit(() -> {
|
||||||
List<Exception> exceptions = new ArrayList<>();
|
List<Exception> exceptions = new ArrayList<>();
|
||||||
try {
|
try {
|
||||||
for (JavaFxRecording javaFxRecording : recordings) {
|
for (JavaFxRecording javaFxRecording : recordings) {
|
||||||
|
@ -595,7 +595,6 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
backgroundThread.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void jumpToNextModel(KeyCode code) {
|
private void jumpToNextModel(KeyCode code) {
|
||||||
|
@ -646,11 +645,11 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
||||||
|
|
||||||
private void onOpenDirectory(JavaFxRecording first) {
|
private void onOpenDirectory(JavaFxRecording first) {
|
||||||
File tsFile = first.getAbsoluteFile();
|
File tsFile = first.getAbsoluteFile();
|
||||||
new Thread(() -> DesktopIntegration.open(tsFile.getParent())).start();
|
GlobalThreadPool.submit(() -> DesktopIntegration.open(tsFile.getParent()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void triggerPostProcessing(List<JavaFxRecording> recs) {
|
private void triggerPostProcessing(List<JavaFxRecording> recs) {
|
||||||
new Thread(() -> {
|
GlobalThreadPool.submit(() -> {
|
||||||
for (JavaFxRecording rec : recs) {
|
for (JavaFxRecording rec : recs) {
|
||||||
try {
|
try {
|
||||||
recorder.rerunPostProcessing(rec.getDelegate());
|
recorder.rerunPostProcessing(rec.getDelegate());
|
||||||
|
@ -659,7 +658,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
||||||
LOG.error("Error while starting post-processing", e1);
|
LOG.error("Error while starting post-processing", e1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).start();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void download(Recording recording) {
|
private void download(Recording recording) {
|
||||||
|
@ -761,12 +760,12 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void play(Recording recording) {
|
private void play(Recording recording) {
|
||||||
new Thread(() -> {
|
GlobalThreadPool.submit(() -> {
|
||||||
boolean started = Player.play(recording);
|
boolean started = Player.play(recording);
|
||||||
if (started && Config.getInstance().getSettings().showPlayerStarting) {
|
if (started && Config.getInstance().getSettings().showPlayerStarting) {
|
||||||
Platform.runLater(() -> Toast.makeText(getTabPane().getScene(), "Starting Player", 2000, 500, 500));
|
Platform.runLater(() -> Toast.makeText(getTabPane().getScene(), "Starting Player", 2000, 500, 500));
|
||||||
}
|
}
|
||||||
}).start();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void play(Model model) {
|
private void play(Model model) {
|
||||||
|
@ -795,7 +794,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteAsync(List<JavaFxRecording> recordings) {
|
private void deleteAsync(List<JavaFxRecording> recordings) {
|
||||||
Thread deleteThread = new Thread(() -> {
|
GlobalThreadPool.submit(() -> {
|
||||||
recordingsLock.lock();
|
recordingsLock.lock();
|
||||||
try {
|
try {
|
||||||
List<Recording> deleted = new ArrayList<>();
|
List<Recording> deleted = new ArrayList<>();
|
||||||
|
@ -821,10 +820,10 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
||||||
Platform.runLater(() -> table.setCursor(Cursor.DEFAULT));
|
Platform.runLater(() -> table.setCursor(Cursor.DEFAULT));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
deleteThread.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveState() {
|
@Override
|
||||||
|
public void onShutdown() {
|
||||||
if (!table.getSortOrder().isEmpty()) {
|
if (!table.getSortOrder().isEmpty()) {
|
||||||
TableColumn<JavaFxRecording, ?> col = table.getSortOrder().get(0);
|
TableColumn<JavaFxRecording, ?> col = table.getSortOrder().get(0);
|
||||||
Config.getInstance().getSettings().recordingsSortColumn = col.getText();
|
Config.getInstance().getSettings().recordingsSortColumn = col.getText();
|
||||||
|
|
|
@ -8,8 +8,6 @@ import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
@ -21,6 +19,7 @@ import com.google.common.cache.CacheLoader;
|
||||||
import com.google.common.cache.LoadingCache;
|
import com.google.common.cache.LoadingCache;
|
||||||
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
|
import ctbrec.GlobalThreadPool;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.Model.State;
|
import ctbrec.Model.State;
|
||||||
import ctbrec.io.HttpException;
|
import ctbrec.io.HttpException;
|
||||||
|
@ -106,7 +105,6 @@ public class ThumbCell extends StackPane {
|
||||||
private ObservableList<Node> thumbCellList;
|
private ObservableList<Node> thumbCellList;
|
||||||
private boolean mouseHovering = false;
|
private boolean mouseHovering = false;
|
||||||
private boolean recording = false;
|
private boolean recording = false;
|
||||||
private static ExecutorService imageLoadingThreadPool = Executors.newFixedThreadPool(30);
|
|
||||||
static LoadingCache<Model, int[]> resolutionCache = CacheBuilder.newBuilder()
|
static LoadingCache<Model, int[]> resolutionCache = CacheBuilder.newBuilder()
|
||||||
.expireAfterAccess(5, TimeUnit.MINUTES)
|
.expireAfterAccess(5, TimeUnit.MINUTES)
|
||||||
.maximumSize(10000)
|
.maximumSize(10000)
|
||||||
|
@ -285,7 +283,7 @@ public class ThumbCell extends StackPane {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}).whenComplete((result, exception) -> {
|
}, GlobalThreadPool.get()).whenComplete((result, exception) -> {
|
||||||
startPreview = null;
|
startPreview = null;
|
||||||
if (result.booleanValue()) {
|
if (result.booleanValue()) {
|
||||||
setPreviewVisible(previewTrigger, true);
|
setPreviewVisible(previewTrigger, true);
|
||||||
|
@ -387,7 +385,7 @@ public class ThumbCell extends StackPane {
|
||||||
if (!Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
|
if (!Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
|
||||||
boolean updateThumbs = Config.getInstance().getSettings().updateThumbnails;
|
boolean updateThumbs = Config.getInstance().getSettings().updateThumbnails;
|
||||||
if (updateThumbs || iv.getImage() == null) {
|
if (updateThumbs || iv.getImage() == null) {
|
||||||
imageLoadingThreadPool.submit(createThumbDownload(url));
|
GlobalThreadPool.submit(createThumbDownload(url));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -512,7 +510,7 @@ public class ThumbCell extends StackPane {
|
||||||
|
|
||||||
void pauseResumeAction(boolean pause) {
|
void pauseResumeAction(boolean pause) {
|
||||||
setCursor(Cursor.WAIT);
|
setCursor(Cursor.WAIT);
|
||||||
new Thread(() -> {
|
GlobalThreadPool.submit(() -> {
|
||||||
try {
|
try {
|
||||||
if (pause) {
|
if (pause) {
|
||||||
recorder.suspendRecording(model);
|
recorder.suspendRecording(model);
|
||||||
|
@ -532,11 +530,11 @@ public class ThumbCell extends StackPane {
|
||||||
} finally {
|
} finally {
|
||||||
Platform.runLater(() -> setCursor(Cursor.DEFAULT));
|
Platform.runLater(() -> setCursor(Cursor.DEFAULT));
|
||||||
}
|
}
|
||||||
}).start();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startStopActionAsync(Model model, boolean start) {
|
private void startStopActionAsync(Model model, boolean start) {
|
||||||
new Thread(() -> {
|
GlobalThreadPool.submit(() -> {
|
||||||
try {
|
try {
|
||||||
if (start) {
|
if (start) {
|
||||||
recorder.addModel(model);
|
recorder.addModel(model);
|
||||||
|
@ -552,7 +550,7 @@ public class ThumbCell extends StackPane {
|
||||||
} finally {
|
} finally {
|
||||||
Platform.runLater(() -> setCursor(Cursor.DEFAULT));
|
Platform.runLater(() -> setCursor(Cursor.DEFAULT));
|
||||||
}
|
}
|
||||||
}).start();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
CompletableFuture<Boolean> follow(boolean follow) {
|
CompletableFuture<Boolean> follow(boolean follow) {
|
||||||
|
@ -587,7 +585,7 @@ public class ThumbCell extends StackPane {
|
||||||
} finally {
|
} finally {
|
||||||
Platform.runLater(() -> setCursor(Cursor.DEFAULT));
|
Platform.runLater(() -> setCursor(Cursor.DEFAULT));
|
||||||
}
|
}
|
||||||
});
|
}, GlobalThreadPool.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
void recordLater(boolean recordLater) {
|
void recordLater(boolean recordLater) {
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
|
import ctbrec.GlobalThreadPool;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.event.EventBusHolder;
|
import ctbrec.event.EventBusHolder;
|
||||||
import ctbrec.recorder.Recorder;
|
import ctbrec.recorder.Recorder;
|
||||||
|
@ -299,7 +300,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
searchTask = new ThumbOverviewTabSearchTask(site, popover, popoverTreeList, newValue);
|
searchTask = new ThumbOverviewTabSearchTask(site, popover, popoverTreeList, newValue);
|
||||||
new Thread(searchTask).start();
|
GlobalThreadPool.submit(searchTask);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -602,7 +603,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
|
||||||
private MenuItem createTipMenuItem(ThumbCell cell) {
|
private MenuItem createTipMenuItem(ThumbCell cell) {
|
||||||
MenuItem sendTip = new MenuItem("Send Tip");
|
MenuItem sendTip = new MenuItem("Send Tip");
|
||||||
sendTip.setOnAction(e -> {
|
sendTip.setOnAction(e -> {
|
||||||
TipDialog tipDialog = new TipDialog(getTabPane().getScene(), site, cell.getModel());
|
TipDialog tipDialog = new TipDialog(getTabPane().getScene(), site);
|
||||||
tipDialog.showAndWait();
|
tipDialog.showAndWait();
|
||||||
String tipText = tipDialog.getResult();
|
String tipText = tipDialog.getResult();
|
||||||
if(tipText != null) {
|
if(tipText != null) {
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
package ctbrec.ui.tabs;
|
package ctbrec.ui.tabs;
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import ctbrec.GlobalThreadPool;
|
||||||
import ctbrec.io.HttpException;
|
import ctbrec.io.HttpException;
|
||||||
import ctbrec.ui.CamrecApplication;
|
import ctbrec.ui.CamrecApplication;
|
||||||
import ctbrec.ui.CamrecApplication.Release;
|
import ctbrec.ui.CamrecApplication.Release;
|
||||||
|
@ -47,7 +46,7 @@ public class UpdateTab extends Tab implements TabSelectionListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadChangeLog() {
|
public void loadChangeLog() {
|
||||||
CompletableFuture.runAsync(() -> {
|
GlobalThreadPool.submit(() -> {
|
||||||
Request req = new Request.Builder().url("https://pastebin.com/raw/fiAPtM0s").build();
|
Request req = new Request.Builder().url("https://pastebin.com/raw/fiAPtM0s").build();
|
||||||
try (Response resp = CamrecApplication.httpClient.execute(req)) {
|
try (Response resp = CamrecApplication.httpClient.execute(req)) {
|
||||||
if (resp.isSuccessful()) {
|
if (resp.isSuccessful()) {
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
package ctbrec;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
public class GlobalThreadPool {
|
||||||
|
|
||||||
|
private static ExecutorService threadPool = Executors.newFixedThreadPool(30, r -> {
|
||||||
|
Thread t = new Thread(r);
|
||||||
|
t.setDaemon(true);
|
||||||
|
t.setName("GlobalWorker-" + UUID.randomUUID().toString().substring(0, 8));
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
|
||||||
|
private GlobalThreadPool() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Future<?> submit(Runnable runnable) { // NOSONAR
|
||||||
|
return threadPool.submit(runnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Future<T> submit(Callable<T> callable) {
|
||||||
|
return threadPool.submit(callable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ExecutorService get() {
|
||||||
|
return threadPool;
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ package ctbrec;
|
||||||
import static ctbrec.Recording.State.*;
|
import static ctbrec.Recording.State.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
@ -14,6 +15,9 @@ import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import ctbrec.event.EventBusHolder;
|
import ctbrec.event.EventBusHolder;
|
||||||
import ctbrec.event.RecordingStateChangedEvent;
|
import ctbrec.event.RecordingStateChangedEvent;
|
||||||
import ctbrec.io.IoUtils;
|
import ctbrec.io.IoUtils;
|
||||||
|
@ -21,6 +25,8 @@ import ctbrec.recorder.download.Download;
|
||||||
import ctbrec.recorder.download.VideoLengthDetector;
|
import ctbrec.recorder.download.VideoLengthDetector;
|
||||||
|
|
||||||
public class Recording implements Serializable, Callable<Recording> {
|
public class Recording implements Serializable, Callable<Recording> {
|
||||||
|
private static final transient Logger LOG = LoggerFactory.getLogger(Recording.class);
|
||||||
|
|
||||||
private String id;
|
private String id;
|
||||||
private Model model;
|
private Model model;
|
||||||
private transient Download download;
|
private transient Download download;
|
||||||
|
@ -104,10 +110,6 @@ public class Recording implements Serializable, Callable<Recording> {
|
||||||
this.progress = progress;
|
this.progress = progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
// public String getPath() {
|
|
||||||
// return path;
|
|
||||||
// }
|
|
||||||
|
|
||||||
public void setPath(String path) {
|
public void setPath(String path) {
|
||||||
this.path = path;
|
this.path = path;
|
||||||
}
|
}
|
||||||
|
@ -246,22 +248,43 @@ public class Recording implements Serializable, Callable<Recording> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private long getSize() {
|
private long getSize() {
|
||||||
File rec = getAbsoluteFile();
|
try {
|
||||||
if (rec.isDirectory()) {
|
Set<File> files = getAllRecordingFiles();
|
||||||
return IoUtils.getDirectorySize(rec);
|
long sum = 0;
|
||||||
} else {
|
for (File file : files) {
|
||||||
if (!rec.exists()) {
|
if (file.isDirectory()) {
|
||||||
if (rec.getName().endsWith(".m3u8")) {
|
sum += IoUtils.getDirectorySize(file);
|
||||||
return IoUtils.getDirectorySize(rec.getParentFile());
|
|
||||||
} else {
|
} else {
|
||||||
return -1;
|
if (!file.exists()) {
|
||||||
|
if (file.getName().endsWith(".m3u8")) {
|
||||||
|
sum += IoUtils.getDirectorySize(file.getParentFile());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sum += file.length();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return rec.length();
|
|
||||||
}
|
}
|
||||||
|
return sum;
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Couldn't determine recording size", e);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Set<File> getAllRecordingFiles() throws IOException {
|
||||||
|
Set<File> files = new HashSet<>();
|
||||||
|
if (absoluteFile != null) {
|
||||||
|
files.add(absoluteFile.getCanonicalFile());
|
||||||
|
}
|
||||||
|
if (postProcessedFile != null) {
|
||||||
|
files.add(postProcessedFile.getCanonicalFile());
|
||||||
|
}
|
||||||
|
for (String associatedFile : associatedFiles) {
|
||||||
|
files.add(new File(associatedFile).getCanonicalFile());
|
||||||
|
}
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
public void refresh() {
|
public void refresh() {
|
||||||
sizeInByte = getSize();
|
sizeInByte = getSize();
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,6 +120,7 @@ public class Settings {
|
||||||
public String proxyPort;
|
public String proxyPort;
|
||||||
public ProxyType proxyType = ProxyType.DIRECT;
|
public ProxyType proxyType = ProxyType.DIRECT;
|
||||||
public String proxyUser;
|
public String proxyUser;
|
||||||
|
public boolean recentlyWatched = true;
|
||||||
public double[] recordLaterColumnWidths = new double[0];
|
public double[] recordLaterColumnWidths = new double[0];
|
||||||
public String[] recordLaterColumnIds = new String[0];
|
public String[] recordLaterColumnIds = new String[0];
|
||||||
public String recordLaterSortColumn = "";
|
public String recordLaterSortColumn = "";
|
||||||
|
|
|
@ -1,15 +1,7 @@
|
||||||
package ctbrec.io;
|
package ctbrec.io;
|
||||||
|
|
||||||
import com.squareup.moshi.JsonAdapter;
|
import static java.nio.charset.StandardCharsets.*;
|
||||||
import com.squareup.moshi.Moshi;
|
|
||||||
import ctbrec.Config;
|
|
||||||
import ctbrec.Settings.ProxyType;
|
|
||||||
import okhttp3.*;
|
|
||||||
import okhttp3.OkHttpClient.Builder;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.net.ssl.*;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -21,11 +13,40 @@ import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import javax.net.ssl.KeyManager;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonAdapter;
|
||||||
|
import com.squareup.moshi.Moshi;
|
||||||
|
|
||||||
|
import ctbrec.Config;
|
||||||
|
import ctbrec.Settings.ProxyType;
|
||||||
|
import okhttp3.ConnectionPool;
|
||||||
|
import okhttp3.Cookie;
|
||||||
|
import okhttp3.Credentials;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.OkHttpClient.Builder;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import okhttp3.Route;
|
||||||
|
import okhttp3.WebSocket;
|
||||||
|
import okhttp3.WebSocketListener;
|
||||||
|
|
||||||
public abstract class HttpClient {
|
public abstract class HttpClient {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(HttpClient.class);
|
private static final Logger LOG = LoggerFactory.getLogger(HttpClient.class);
|
||||||
|
@ -275,10 +296,22 @@ public abstract class HttpClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String bodyToJsonObject(Response response) {
|
public static String bodyToJsonObject(Response response) {
|
||||||
return Optional.ofNullable(response.body()).map(Object::toString).orElse("{}");
|
return Optional.ofNullable(response.body()).map(b -> {
|
||||||
|
try {
|
||||||
|
return b.string();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return "{}";
|
||||||
|
}
|
||||||
|
}).orElse("{}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String bodyToJsonArray(Response response) {
|
public static String bodyToJsonArray(Response response) {
|
||||||
return Optional.ofNullable(response.body()).map(Object::toString).orElse("[]");
|
return Optional.ofNullable(response.body()).map(b -> {
|
||||||
|
try {
|
||||||
|
return b.string();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return "[]";
|
||||||
|
}
|
||||||
|
}).orElse("[]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,6 @@ import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
@ -216,6 +215,7 @@ public class NextGenLocalRecorder implements Recorder {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
recording.refresh();
|
||||||
if (recording.getStatus() != State.DELETED) {
|
if (recording.getStatus() != State.DELETED) {
|
||||||
setRecordingStatus(recording, State.FINISHED);
|
setRecordingStatus(recording, State.FINISHED);
|
||||||
recordingManager.saveRecording(recording);
|
recordingManager.saveRecording(recording);
|
||||||
|
@ -448,58 +448,40 @@ public class NextGenLocalRecorder implements Recorder {
|
||||||
recording = false;
|
recording = false;
|
||||||
|
|
||||||
if (!immediately) {
|
if (!immediately) {
|
||||||
LOG.debug("Stopping all recording processes");
|
stopRecordingProcesses();
|
||||||
recorderLock.lock();
|
awaitDownloadsFinish();
|
||||||
try {
|
shutdownThreadPools();
|
||||||
// make a copy to avoid ConcurrentModificationException
|
}
|
||||||
List<Recording> toStop = new ArrayList<>(recordingProcesses.values());
|
}
|
||||||
if (!toStop.isEmpty()) {
|
|
||||||
ExecutorService shutdownPool = Executors.newFixedThreadPool(toStop.size());
|
|
||||||
List<Future<?>> shutdownFutures = new ArrayList<>(toStop.size());
|
|
||||||
for (Recording rec : toStop) {
|
|
||||||
Optional.ofNullable(rec.getDownload()).ifPresent(d -> {
|
|
||||||
shutdownFutures.add(shutdownPool.submit(() -> d.stop()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
shutdownPool.shutdown();
|
|
||||||
try {
|
|
||||||
shutdownPool.awaitTermination(10, TimeUnit.MINUTES);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
recorderLock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait for downloads to finish
|
private void awaitDownloadsFinish() {
|
||||||
LOG.info("Waiting for downloads to finish");
|
LOG.info("Waiting for downloads to finish");
|
||||||
for (int i = 0; i < 60; i++) {
|
for (int i = 0; i < 60; i++) {
|
||||||
if (!recordingProcesses.isEmpty()) {
|
if (!recordingProcesses.isEmpty()) {
|
||||||
try {
|
try {
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
LOG.error("Error while waiting for downloads to finish", e);
|
LOG.error("Error while waiting for downloads to finish", e);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// shutdown threadpools
|
private void shutdownThreadPools() {
|
||||||
try {
|
try {
|
||||||
LOG.info("Shutting down download pool");
|
LOG.info("Shutting down download pool");
|
||||||
downloadPool.shutdown();
|
downloadPool.shutdown();
|
||||||
client.shutdown();
|
client.shutdown();
|
||||||
downloadPool.awaitTermination(1, TimeUnit.MINUTES);
|
downloadPool.awaitTermination(1, TimeUnit.MINUTES);
|
||||||
LOG.info("Shutting down post-processing pool");
|
LOG.info("Shutting down post-processing pool");
|
||||||
ppPool.shutdown();
|
ppPool.shutdown();
|
||||||
int minutesToWait = 10;
|
int minutesToWait = 10;
|
||||||
LOG.info("Waiting {} minutes (max) for post-processing to finish", minutesToWait);
|
LOG.info("Waiting {} minutes (max) for post-processing to finish", minutesToWait);
|
||||||
ppPool.awaitTermination(minutesToWait, TimeUnit.MINUTES);
|
ppPool.awaitTermination(minutesToWait, TimeUnit.MINUTES);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
LOG.error("Error while waiting for pools to finish", e);
|
LOG.error("Error while waiting for pools to finish", e);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -703,7 +685,6 @@ public class NextGenLocalRecorder implements Recorder {
|
||||||
config.save();
|
config.save();
|
||||||
} else {
|
} else {
|
||||||
LOG.warn("Couldn't change priority for model {}. Not found in list", model.getName());
|
LOG.warn("Couldn't change priority for model {}. Not found in list", model.getName());
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.error("Couldn't save config", e);
|
LOG.error("Couldn't save config", e);
|
||||||
|
|
|
@ -272,7 +272,6 @@ public class DashDownload extends AbstractDownload {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
private boolean splitRecording() {
|
private boolean splitRecording() {
|
||||||
if (splittingStrategy.splitNecessary(this)) {
|
if (splittingStrategy.splitNecessary(this)) {
|
||||||
internalStop();
|
internalStop();
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package ctbrec.recorder.download.hls;
|
package ctbrec.recorder.download.hls;
|
||||||
|
|
||||||
import static ctbrec.recorder.download.StreamSource.*;
|
import static ctbrec.recorder.download.StreamSource.*;
|
||||||
|
import static java.util.concurrent.TimeUnit.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -41,6 +43,8 @@ public class HlsdlDownload extends AbstractDownload {
|
||||||
private transient Hlsdl hlsdl;
|
private transient Hlsdl hlsdl;
|
||||||
protected transient Process hlsdlProcess;
|
protected transient Process hlsdlProcess;
|
||||||
protected transient boolean running = true;
|
protected transient boolean running = true;
|
||||||
|
protected transient Instant lastSizeChange = Instant.now();
|
||||||
|
protected transient long lastSize = 0;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(Config config, Model model, Instant startTime, ExecutorService executorService) throws IOException {
|
public void init(Config config, Model model, Instant startTime, ExecutorService executorService) throws IOException {
|
||||||
|
@ -73,6 +77,17 @@ public class HlsdlDownload extends AbstractDownload {
|
||||||
if (splittingStrategy.splitNecessary(this)) {
|
if (splittingStrategy.splitNecessary(this)) {
|
||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
|
long size = getSizeInByte();
|
||||||
|
if (size != lastSize) {
|
||||||
|
lastSize = size;
|
||||||
|
lastSizeChange = Instant.now();
|
||||||
|
} else {
|
||||||
|
int seconds = 90;
|
||||||
|
if (Duration.between(lastSizeChange, Instant.now()).toMillis() > SECONDS.toMillis(seconds)) {
|
||||||
|
LOG.info("Recording size didn't change for {} secs. Stopping recording for {}", seconds, model);
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (ProcessExitedUncleanException e) {
|
} catch (ProcessExitedUncleanException e) {
|
||||||
LOG.error("hlsdl exited unclean", e);
|
LOG.error("hlsdl exited unclean", e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,18 @@
|
||||||
package ctbrec.sites.cam4;
|
package ctbrec.sites.cam4;
|
||||||
|
|
||||||
|
import static ctbrec.io.HttpClient.*;
|
||||||
|
import static ctbrec.io.HttpConstants.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.StringUtil;
|
import ctbrec.StringUtil;
|
||||||
|
@ -8,18 +21,6 @@ import ctbrec.io.HttpException;
|
||||||
import ctbrec.sites.AbstractSite;
|
import ctbrec.sites.AbstractSite;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
import org.json.JSONArray;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import static ctbrec.io.HttpClient.bodyToJsonObject;
|
|
||||||
import static ctbrec.io.HttpConstants.USER_AGENT;
|
|
||||||
|
|
||||||
public class Cam4 extends AbstractSite {
|
public class Cam4 extends AbstractSite {
|
||||||
|
|
||||||
|
@ -121,27 +122,27 @@ public class Cam4 extends AbstractSite {
|
||||||
|
|
||||||
private void search(String q, boolean offline, List<Model> models) throws IOException {
|
private void search(String q, boolean offline, List<Model> models) throws IOException {
|
||||||
String url = BASE_URI + "/usernameSearch?username=" + URLEncoder.encode(q, "utf-8");
|
String url = BASE_URI + "/usernameSearch?username=" + URLEncoder.encode(q, "utf-8");
|
||||||
if(offline) {
|
if (offline) {
|
||||||
url += "&offline=true";
|
url += "&offline=true";
|
||||||
}
|
}
|
||||||
Request req = new Request.Builder()
|
Request req = new Request.Builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
.addHeader(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
.addHeader(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
||||||
.build();
|
.build();
|
||||||
try(Response response = getHttpClient().execute(req)) {
|
try (Response response = getHttpClient().execute(req)) {
|
||||||
if(response.isSuccessful()) {
|
if (response.isSuccessful()) {
|
||||||
String body = bodyToJsonObject(response);
|
String body = bodyToJsonArray(response);
|
||||||
JSONArray results = new JSONArray(body);
|
JSONArray results = new JSONArray(body);
|
||||||
for (int i = 0; i < results.length(); i++) {
|
for (int i = 0; i < results.length(); i++) {
|
||||||
JSONObject result = results.getJSONObject(i);
|
JSONObject result = results.getJSONObject(i);
|
||||||
Model model = createModel(result.getString("username"));
|
Model model = createModel(result.getString("username"));
|
||||||
String thumb = null;
|
String thumb = null;
|
||||||
if(result.has("thumbnailId")) {
|
if (result.has("thumbnailId")) {
|
||||||
thumb = "https://snapshots.xcdnpro.com/thumbnails/" + model.getName() + "?s=" + result.getString("thumbnailId");
|
thumb = "https://snapshots.xcdnpro.com/thumbnails/" + model.getName() + "?s=" + result.getString("thumbnailId");
|
||||||
} else {
|
} else {
|
||||||
thumb = result.getString("profileImageLink");
|
thumb = result.getString("profileImageLink");
|
||||||
}
|
}
|
||||||
if(StringUtil.isNotBlank(thumb)) {
|
if (StringUtil.isNotBlank(thumb)) {
|
||||||
model.setPreview(thumb);
|
model.setPreview(thumb);
|
||||||
}
|
}
|
||||||
models.add(model);
|
models.add(model);
|
||||||
|
|
|
@ -191,9 +191,13 @@ public class Cam4Model extends AbstractModel {
|
||||||
StreamSource src = new StreamSource();
|
StreamSource src = new StreamSource();
|
||||||
src.bandwidth = playlist.getStreamInfo().getBandwidth();
|
src.bandwidth = playlist.getStreamInfo().getBandwidth();
|
||||||
src.height = Optional.ofNullable(playlist.getStreamInfo()).map(StreamInfo::getResolution).map(res -> res.height).orElse(0);
|
src.height = Optional.ofNullable(playlist.getStreamInfo()).map(StreamInfo::getResolution).map(res -> res.height).orElse(0);
|
||||||
String masterUrl = getPlaylistUrl();
|
if (playlist.getUri().startsWith("http")) {
|
||||||
String baseUrl = masterUrl.substring(0, masterUrl.lastIndexOf('/') + 1);
|
src.mediaPlaylistUrl = playlist.getUri();
|
||||||
src.mediaPlaylistUrl = baseUrl + playlist.getUri();
|
} else {
|
||||||
|
String masterUrl = getPlaylistUrl();
|
||||||
|
String baseUrl = masterUrl.substring(0, masterUrl.lastIndexOf('/') + 1);
|
||||||
|
src.mediaPlaylistUrl = baseUrl + playlist.getUri();
|
||||||
|
}
|
||||||
LOG.trace("Media playlist {}", src.mediaPlaylistUrl);
|
LOG.trace("Media playlist {}", src.mediaPlaylistUrl);
|
||||||
sources.add(src);
|
sources.add(src);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
|
import ctbrec.GlobalThreadPool;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.io.HttpClient;
|
import ctbrec.io.HttpClient;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
|
@ -78,9 +79,9 @@ public class LiveJasminTippingWebSocket {
|
||||||
LOG.trace("relay <-- {} T{}", model.getName(), text);
|
LOG.trace("relay <-- {} T{}", model.getName(), text);
|
||||||
JSONObject event = new JSONObject(text);
|
JSONObject event = new JSONObject(text);
|
||||||
if (event.optString("event").equals("accept")) {
|
if (event.optString("event").equals("accept")) {
|
||||||
new Thread(() -> {
|
GlobalThreadPool.submit(() -> {
|
||||||
sendToRelay("{\"event\":\"connectSharedObject\",\"name\":\"data/chat_so\"}");
|
sendToRelay("{\"event\":\"connectSharedObject\",\"name\":\"data/chat_so\"}");
|
||||||
}).start();
|
});
|
||||||
} else if(event.optString("event").equals("call")) {
|
} else if(event.optString("event").equals("call")) {
|
||||||
String func = event.optString("funcName");
|
String func = event.optString("funcName");
|
||||||
if (func.equals("setName")) {
|
if (func.equals("setName")) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package ctbrec.recorder.server;
|
package ctbrec.recorder.server;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.*;
|
||||||
import static javax.servlet.http.HttpServletResponse.*;
|
import static javax.servlet.http.HttpServletResponse.*;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
|
@ -53,6 +54,7 @@ import org.slf4j.LoggerFactory;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
|
import ctbrec.NotLoggedInExcetion;
|
||||||
import ctbrec.Version;
|
import ctbrec.Version;
|
||||||
import ctbrec.event.EventBusHolder;
|
import ctbrec.event.EventBusHolder;
|
||||||
import ctbrec.event.EventHandler;
|
import ctbrec.event.EventHandler;
|
||||||
|
@ -124,7 +126,7 @@ public class HttpServer {
|
||||||
if (success) {
|
if (success) {
|
||||||
LOG.info("Successfully logged in to {}", site.getName());
|
LOG.info("Successfully logged in to {}", site.getName());
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("Login returned false");
|
throw new NotLoggedInExcetion("Login returned false");
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.info("Login to {} failed", site.getName());
|
LOG.info("Login to {} failed", site.getName());
|
||||||
|
@ -243,7 +245,7 @@ public class HttpServer {
|
||||||
byte[] hmac = Optional.ofNullable(HttpServer.this.config.getSettings().key).orElse(new byte[0]);
|
byte[] hmac = Optional.ofNullable(HttpServer.this.config.getSettings().key).orElse(new byte[0]);
|
||||||
try {
|
try {
|
||||||
JSONObject response = new JSONObject();
|
JSONObject response = new JSONObject();
|
||||||
response.put("hmac", new String(hmac, "utf-8"));
|
response.put("hmac", new String(hmac, UTF_8));
|
||||||
resp.getOutputStream().println(response.toString());
|
resp.getOutputStream().println(response.toString());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||||
|
|
|
@ -1,23 +1,7 @@
|
||||||
package ctbrec.recorder.server;
|
package ctbrec.recorder.server;
|
||||||
|
|
||||||
import com.squareup.moshi.JsonAdapter;
|
import static javax.servlet.http.HttpServletResponse.*;
|
||||||
import com.squareup.moshi.Moshi;
|
|
||||||
import ctbrec.Config;
|
|
||||||
import ctbrec.Model;
|
|
||||||
import ctbrec.Recording;
|
|
||||||
import ctbrec.io.BandwidthMeter;
|
|
||||||
import ctbrec.io.FileJsonAdapter;
|
|
||||||
import ctbrec.io.InstantJsonAdapter;
|
|
||||||
import ctbrec.io.ModelJsonAdapter;
|
|
||||||
import ctbrec.recorder.Recorder;
|
|
||||||
import ctbrec.sites.Site;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
|
@ -27,7 +11,27 @@ import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import static javax.servlet.http.HttpServletResponse.*;
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonAdapter;
|
||||||
|
import com.squareup.moshi.Moshi;
|
||||||
|
|
||||||
|
import ctbrec.Config;
|
||||||
|
import ctbrec.GlobalThreadPool;
|
||||||
|
import ctbrec.Model;
|
||||||
|
import ctbrec.Recording;
|
||||||
|
import ctbrec.io.BandwidthMeter;
|
||||||
|
import ctbrec.io.FileJsonAdapter;
|
||||||
|
import ctbrec.io.InstantJsonAdapter;
|
||||||
|
import ctbrec.io.ModelJsonAdapter;
|
||||||
|
import ctbrec.recorder.Recorder;
|
||||||
|
import ctbrec.sites.Site;
|
||||||
|
|
||||||
public class RecorderServlet extends AbstractCtbrecServlet {
|
public class RecorderServlet extends AbstractCtbrecServlet {
|
||||||
|
|
||||||
|
@ -87,24 +91,24 @@ public class RecorderServlet extends AbstractCtbrecServlet {
|
||||||
resp.getWriter().write(response);
|
resp.getWriter().write(response);
|
||||||
break;
|
break;
|
||||||
case "stop":
|
case "stop":
|
||||||
new Thread(() -> {
|
GlobalThreadPool.submit(() -> {
|
||||||
try {
|
try {
|
||||||
recorder.stopRecording(request.model);
|
recorder.stopRecording(request.model);
|
||||||
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) {
|
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) {
|
||||||
LOG.error("Couldn't stop recording for model {}", request.model, e);
|
LOG.error("Couldn't stop recording for model {}", request.model, e);
|
||||||
}
|
}
|
||||||
}).start();
|
});
|
||||||
response = "{\"status\": \"success\", \"msg\": \"Stopping recording\"}";
|
response = "{\"status\": \"success\", \"msg\": \"Stopping recording\"}";
|
||||||
resp.getWriter().write(response);
|
resp.getWriter().write(response);
|
||||||
break;
|
break;
|
||||||
case "stopAt":
|
case "stopAt":
|
||||||
new Thread(() -> {
|
GlobalThreadPool.submit(() -> {
|
||||||
try {
|
try {
|
||||||
recorder.stopRecordingAt(request.model);
|
recorder.stopRecordingAt(request.model);
|
||||||
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) {
|
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) {
|
||||||
LOG.error("Couldn't stop recording for model {}", request.model, e);
|
LOG.error("Couldn't stop recording for model {}", request.model, e);
|
||||||
}
|
}
|
||||||
}).start();
|
});
|
||||||
response = "{\"status\": \"success\", \"msg\": \"Stopping recording\"}";
|
response = "{\"status\": \"success\", \"msg\": \"Stopping recording\"}";
|
||||||
resp.getWriter().write(response);
|
resp.getWriter().write(response);
|
||||||
break;
|
break;
|
||||||
|
@ -189,13 +193,13 @@ public class RecorderServlet extends AbstractCtbrecServlet {
|
||||||
break;
|
break;
|
||||||
case "suspend":
|
case "suspend":
|
||||||
LOG.debug("Suspend recording for model {} - {}", request.model.getName(), request.model.getUrl());
|
LOG.debug("Suspend recording for model {} - {}", request.model.getName(), request.model.getUrl());
|
||||||
new Thread(() -> {
|
GlobalThreadPool.submit(() -> {
|
||||||
try {
|
try {
|
||||||
recorder.suspendRecording(request.model);
|
recorder.suspendRecording(request.model);
|
||||||
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) {
|
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) {
|
||||||
LOG.error("Couldn't suspend recording for model {}", request.model, e);
|
LOG.error("Couldn't suspend recording for model {}", request.model, e);
|
||||||
}
|
}
|
||||||
}).start();
|
});
|
||||||
response = "{\"status\": \"success\", \"msg\": \"Suspending recording\"}";
|
response = "{\"status\": \"success\", \"msg\": \"Suspending recording\"}";
|
||||||
resp.getWriter().write(response);
|
resp.getWriter().write(response);
|
||||||
break;
|
break;
|
||||||
|
|
Loading…
Reference in New Issue