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
|
||||
========================
|
||||
* 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
|
||||
========================
|
||||
|
|
|
@ -63,6 +63,7 @@ import ctbrec.ui.news.NewsTab;
|
|||
import ctbrec.ui.settings.SettingsTab;
|
||||
import ctbrec.ui.tabs.DonateTabFx;
|
||||
import ctbrec.ui.tabs.HelpTab;
|
||||
import ctbrec.ui.tabs.RecentlyWatchedTab;
|
||||
import ctbrec.ui.tabs.RecordedTab;
|
||||
import ctbrec.ui.tabs.RecordingsTab;
|
||||
import ctbrec.ui.tabs.SiteTab;
|
||||
|
@ -216,6 +217,9 @@ public class CamrecApplication extends Application {
|
|||
tabPane.getTabs().add(modelsTab);
|
||||
recordingsTab = new RecordingsTab("Recordings", recorder, config);
|
||||
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 NewsTab());
|
||||
tabPane.getTabs().add(new DonateTabFx());
|
||||
|
@ -299,8 +303,11 @@ public class CamrecApplication extends Application {
|
|||
|
||||
final boolean immediately = shutdownNow;
|
||||
new Thread(() -> {
|
||||
modelsTab.saveState();
|
||||
recordingsTab.saveState();
|
||||
for (Tab tab : tabPane.getTabs()) {
|
||||
if (tab instanceof ShutdownListener) {
|
||||
((ShutdownListener) tab).onShutdown();
|
||||
}
|
||||
}
|
||||
onlineMonitor.shutdown();
|
||||
recorder.shutdown(immediately);
|
||||
for (Site site : sites) {
|
||||
|
|
|
@ -28,11 +28,10 @@ public class ExternalBrowser implements AutoCloseable {
|
|||
private static final ExternalBrowser INSTANCE = new ExternalBrowser();
|
||||
private Lock lock = new ReentrantLock();
|
||||
|
||||
private Process p;
|
||||
private Consumer<String> messageListener;
|
||||
private InputStream in;
|
||||
private OutputStream out;
|
||||
private Socket socket;
|
||||
private Socket socket; // NOSONAR
|
||||
private Thread reader;
|
||||
private volatile boolean stopped = true;
|
||||
private volatile boolean browserReady = false;
|
||||
|
@ -53,10 +52,10 @@ public class ExternalBrowser implements AutoCloseable {
|
|||
|
||||
File configDir = new File(Config.getInstance().getConfigDir(), "ctbrec-minimal-browser");
|
||||
String[] cmdline = OS.getBrowserCommand(configDir.getCanonicalPath());
|
||||
p = new ProcessBuilder(cmdline).start();
|
||||
Process p = new ProcessBuilder(cmdline).start();
|
||||
if (LOG.isTraceEnabled()) {
|
||||
new Thread(new StreamRedirector(p.getInputStream(), System.out)).start();
|
||||
new Thread(new StreamRedirector(p.getErrorStream(), System.err)).start();
|
||||
new Thread(new StreamRedirector(p.getInputStream(), System.out)).start(); // NOSONAR
|
||||
new Thread(new StreamRedirector(p.getErrorStream(), System.err)).start(); // NOSONAR
|
||||
} else {
|
||||
new Thread(new StreamRedirector(p.getInputStream(), 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");
|
||||
p.waitFor();
|
||||
int exitValue = p.exitValue();
|
||||
p = null;
|
||||
reader = null;
|
||||
in = null;
|
||||
out = null;
|
||||
|
@ -119,7 +117,6 @@ public class ExternalBrowser implements AutoCloseable {
|
|||
}
|
||||
|
||||
public void executeJavaScript(String javaScript) throws IOException {
|
||||
//LOG.debug("Executing JS {}", javaScript);
|
||||
JSONObject script = new JSONObject();
|
||||
script.put("execute", javaScript);
|
||||
out.write(script.toString().getBytes(UTF_8));
|
||||
|
@ -141,8 +138,7 @@ public class ExternalBrowser implements AutoCloseable {
|
|||
|
||||
private void readBrowserOutput() {
|
||||
LOG.debug("Browser output reader started");
|
||||
try {
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(in));
|
||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(in))) {
|
||||
String line;
|
||||
synchronized (browserReadyLock) {
|
||||
browserReady = true;
|
||||
|
@ -150,11 +146,8 @@ public class ExternalBrowser implements AutoCloseable {
|
|||
}
|
||||
while( !Thread.interrupted() && (line = br.readLine()) != null ) {
|
||||
LOG.debug("Browser output: {}", line);
|
||||
if(!line.startsWith("{")) {
|
||||
} else {
|
||||
if(messageListener != null) {
|
||||
messageListener.accept(line);
|
||||
}
|
||||
if (line.startsWith("{") && messageListener != null) {
|
||||
messageListener.accept(line);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
|
|
|
@ -25,10 +25,12 @@ import ctbrec.Config;
|
|||
import ctbrec.Model;
|
||||
import ctbrec.OS;
|
||||
import ctbrec.Recording;
|
||||
import ctbrec.event.EventBusHolder;
|
||||
import ctbrec.io.StreamRedirector;
|
||||
import ctbrec.io.UrlUtil;
|
||||
import ctbrec.recorder.download.StreamSource;
|
||||
import ctbrec.ui.controls.Dialogs;
|
||||
import ctbrec.ui.event.PlayerStartedEvent;
|
||||
import javafx.scene.Scene;
|
||||
|
||||
public class Player {
|
||||
|
@ -85,6 +87,7 @@ public class Player {
|
|||
}
|
||||
String playlistUrl = getPlaylistUrl(model);
|
||||
LOG.debug("Playing {}", playlistUrl);
|
||||
EventBusHolder.BUS.post(new PlayerStartedEvent(model));
|
||||
return Player.play(playlistUrl, async);
|
||||
} else {
|
||||
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,
|
||||
// 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 StreamRedirectThread(playerProcess.getInputStream(), System.out));
|
||||
std.setName("Player stdout pipe");
|
||||
std.setDaemon(true);
|
||||
std.start();
|
||||
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.setDaemon(true);
|
||||
err.start();
|
||||
|
|
|
@ -22,7 +22,7 @@ import javafx.scene.layout.StackPane;
|
|||
import javafx.stage.Popup;
|
||||
|
||||
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 long timeForPopupOpen = TimeUnit.SECONDS.toMillis(1);
|
||||
|
@ -67,11 +67,11 @@ public class PreviewPopupHandler implements EventHandler<MouseEvent> {
|
|||
} else if(event.getEventType() == MouseEvent.MOUSE_ENTERED) {
|
||||
popup.setX(event.getScreenX()+ offset);
|
||||
popup.setY(event.getScreenY()+ offset);
|
||||
JavaFxModel model = getModel(event);
|
||||
if(model != null) {
|
||||
JavaFxModel newModel = getModel(event);
|
||||
if(newModel != null) {
|
||||
closeCountdown = -1;
|
||||
boolean modelChanged = model != this.model;
|
||||
this.model = model;
|
||||
boolean modelChanged = newModel != this.model;
|
||||
this.model = newModel;
|
||||
if(popup.isShowing()) {
|
||||
openCountdown = -1;
|
||||
if(modelChanged) {
|
||||
|
@ -97,15 +97,15 @@ public class PreviewPopupHandler implements EventHandler<MouseEvent> {
|
|||
@SuppressWarnings("unchecked")
|
||||
TableRow<JavaFxModel> row = (TableRow<JavaFxModel>) event.getSource();
|
||||
TableView<JavaFxModel> table = row.getTableView();
|
||||
double offset = 0;
|
||||
double columnOffset = 0;
|
||||
double width = 0;
|
||||
for (TableColumn<JavaFxModel, ?> col : table.getColumns()) {
|
||||
offset += width;
|
||||
columnOffset += width;
|
||||
width = col.getWidth();
|
||||
if(Objects.equals(col.getId(), "preview")) {
|
||||
Point2D screenToLocal = table.screenToLocal(event.getScreenX(), event.getScreenY());
|
||||
double x = screenToLocal.getX();
|
||||
return x >= offset && x <= offset + width;
|
||||
return x >= columnOffset && x <= columnOffset + width;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
@ -176,6 +176,7 @@ public class PreviewPopupHandler implements EventHandler<MouseEvent> {
|
|||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
LOG.error("PreviewPopupTimer interrupted");
|
||||
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.function.Function;
|
||||
|
||||
import ctbrec.GlobalThreadPool;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.recorder.download.StreamSource;
|
||||
import ctbrec.ui.controls.Dialogs;
|
||||
|
@ -19,9 +20,10 @@ import javafx.stage.Stage;
|
|||
public class StreamSourceSelectionDialog {
|
||||
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>>() {
|
||||
@Override
|
||||
protected List<StreamSource> call() throws Exception {
|
||||
|
@ -35,7 +37,7 @@ public class StreamSourceSelectionDialog {
|
|||
List<StreamSource> sources;
|
||||
try {
|
||||
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.setTitle("Stream Quality");
|
||||
choiceDialog.setHeaderText("Select your preferred stream quality");
|
||||
|
@ -45,7 +47,7 @@ public class StreamSourceSelectionDialog {
|
|||
InputStream icon = Dialogs.class.getResourceAsStream("/icon.png");
|
||||
stage.getIcons().add(new Image(icon));
|
||||
Optional<StreamSource> selectedSource = choiceDialog.showAndWait();
|
||||
if(selectedSource.isPresent()) {
|
||||
if (selectedSource.isPresent()) {
|
||||
int index = -1;
|
||||
if (selectedSource.get() != BEST) {
|
||||
index = sources.indexOf(selectedSource.get());
|
||||
|
@ -61,7 +63,7 @@ public class StreamSourceSelectionDialog {
|
|||
}
|
||||
});
|
||||
selectStreamSource.setOnFailed(e -> onFail.apply(selectStreamSource.getException()));
|
||||
new Thread(selectStreamSource).start();
|
||||
GlobalThreadPool.submit(selectStreamSource);
|
||||
}
|
||||
|
||||
private static class BestStreamSource extends StreamSource {
|
||||
|
|
|
@ -7,7 +7,7 @@ import java.util.concurrent.ExecutionException;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ctbrec.Model;
|
||||
import ctbrec.GlobalThreadPool;
|
||||
import ctbrec.sites.Site;
|
||||
import javafx.application.Platform;
|
||||
import javafx.concurrent.Task;
|
||||
|
@ -19,11 +19,11 @@ import javafx.stage.Stage;
|
|||
|
||||
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 Scene parent;
|
||||
|
||||
public TipDialog(Scene parent, Site site, Model model) {
|
||||
public TipDialog(Scene parent, Site site) {
|
||||
this.parent = parent;
|
||||
this.site = site;
|
||||
setTitle("Send Tip");
|
||||
|
@ -32,7 +32,7 @@ public class TipDialog extends TextInputDialog {
|
|||
setContentText("Amount of tokens to tip:");
|
||||
setResizable(true);
|
||||
getEditor().setDisable(true);
|
||||
if(parent != null) {
|
||||
if (parent != null) {
|
||||
Stage stage = (Stage) getDialogPane().getScene().getWindow();
|
||||
stage.getScene().getStylesheets().addAll(parent.getStylesheets());
|
||||
}
|
||||
|
@ -56,14 +56,14 @@ public class TipDialog extends TextInputDialog {
|
|||
double tokens = get();
|
||||
Platform.runLater(() -> {
|
||||
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.";
|
||||
Alert buyTokens = new AutosizeAlert(Alert.AlertType.CONFIRMATION, msg, parent, ButtonType.NO, ButtonType.YES);
|
||||
buyTokens.setTitle("No tokens");
|
||||
buyTokens.setHeaderText("You don't have any tokens");
|
||||
buyTokens.showAndWait();
|
||||
TipDialog.this.close();
|
||||
if(buyTokens.getResult() == ButtonType.YES) {
|
||||
if (buyTokens.getResult() == ButtonType.YES) {
|
||||
DesktopIntegration.open(site.getAffiliateLink());
|
||||
}
|
||||
} else {
|
||||
|
@ -72,13 +72,20 @@ public class TipDialog extends TextInputDialog {
|
|||
setHeaderText("Current token balance: " + df.format(tokens));
|
||||
}
|
||||
});
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
LOG.error("Couldn't retrieve account balance", e);
|
||||
showErrorDialog(e);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
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) {
|
||||
|
|
|
@ -10,6 +10,7 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
|
||||
import ctbrec.GlobalThreadPool;
|
||||
import ctbrec.event.EventBusHolder;
|
||||
import ctbrec.sites.Site;
|
||||
import javafx.application.Platform;
|
||||
|
@ -19,7 +20,7 @@ import javafx.scene.control.Tooltip;
|
|||
|
||||
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 Site site;
|
||||
|
||||
|
@ -72,17 +73,24 @@ public class TokenLabel extends Label {
|
|||
@Override
|
||||
protected void done() {
|
||||
try {
|
||||
double tokens = get();
|
||||
tokens = get();
|
||||
update(tokens);
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
LOG.error("Couldn't retrieve account balance", e);
|
||||
Platform.runLater(() -> {
|
||||
setText("Tokens: error");
|
||||
setTooltip(new Tooltip(e.getMessage()));
|
||||
});
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
handleException(e);
|
||||
} catch (ExecutionException e) {
|
||||
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.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ctbrec.GlobalThreadPool;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.recorder.Recorder;
|
||||
import ctbrec.ui.controls.Dialogs;
|
||||
|
@ -32,7 +32,7 @@ public class CheckModelAccountAction {
|
|||
public void execute(Predicate<Model> filter) {
|
||||
String buttonText = b.getText();
|
||||
b.setDisable(true);
|
||||
CompletableFuture.runAsync(() -> {
|
||||
Runnable checker = (() -> {
|
||||
List<Model> deletedAccounts = new ArrayList<>();
|
||||
try {
|
||||
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.ui.JavaFxModel;
|
||||
import ctbrec.ui.controls.Dialogs;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.TableView;
|
||||
|
@ -30,23 +29,21 @@ public class EditNotesAction {
|
|||
|
||||
public void execute() {
|
||||
source.setCursor(Cursor.WAIT);
|
||||
new Thread(() -> Platform.runLater(() -> {
|
||||
String notes = Config.getInstance().getSettings().modelNotes.getOrDefault(model.getUrl(), "");
|
||||
Optional<String> newNotes = Dialogs.showTextInput(source.getScene(), "Model Notes", "Notes for " + model.getName(), notes);
|
||||
newNotes.ifPresent(n -> {
|
||||
if (!n.trim().isEmpty()) {
|
||||
Config.getInstance().getSettings().modelNotes.put(model.getUrl(), n);
|
||||
} else {
|
||||
Config.getInstance().getSettings().modelNotes.remove(model.getUrl());
|
||||
}
|
||||
try {
|
||||
Config.getInstance().save();
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Couldn't save config", e);
|
||||
}
|
||||
});
|
||||
table.refresh();
|
||||
source.setCursor(Cursor.DEFAULT);
|
||||
})).start();
|
||||
String notes = Config.getInstance().getSettings().modelNotes.getOrDefault(model.getUrl(), "");
|
||||
Optional<String> newNotes = Dialogs.showTextInput(source.getScene(), "Model Notes", "Notes for " + model.getName(), notes);
|
||||
newNotes.ifPresent(n -> {
|
||||
if (!n.trim().isEmpty()) {
|
||||
Config.getInstance().getSettings().modelNotes.put(model.getUrl(), n);
|
||||
} else {
|
||||
Config.getInstance().getSettings().modelNotes.remove(model.getUrl());
|
||||
}
|
||||
try {
|
||||
Config.getInstance().save();
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Couldn't save config", e);
|
||||
}
|
||||
});
|
||||
table.refresh();
|
||||
source.setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,13 +2,9 @@ package ctbrec.ui.action;
|
|||
|
||||
import java.util.List;
|
||||
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 ctbrec.GlobalThreadPool;
|
||||
import ctbrec.Model;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Cursor;
|
||||
|
@ -16,9 +12,6 @@ import javafx.scene.Node;
|
|||
|
||||
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 Consumer<Model> action;
|
||||
protected Node source;
|
||||
|
@ -42,7 +35,7 @@ public class ModelMassEditAction {
|
|||
Consumer<Model> cb = Objects.requireNonNull(callback, "Callback is null, call execute() instead");
|
||||
source.setCursor(Cursor.WAIT);
|
||||
for (Model model : models) {
|
||||
threadPool.submit(() -> {
|
||||
GlobalThreadPool.submit(() -> {
|
||||
action.accept(model);
|
||||
cb.accept(model);
|
||||
Platform.runLater(() -> source.setCursor(Cursor.DEFAULT));
|
||||
|
|
|
@ -4,6 +4,7 @@ import java.io.File;
|
|||
import java.time.Instant;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.GlobalThreadPool;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.Settings.DirectoryStructure;
|
||||
import ctbrec.ui.DesktopIntegration;
|
||||
|
@ -26,7 +27,7 @@ public class OpenRecordingsDir {
|
|||
File fileForRecording = Config.getInstance().getFileForRecording(selectedModel, ".mp4", Instant.now());
|
||||
final File dir = getModelDirectory(fileForRecording);
|
||||
if (dir.exists()) {
|
||||
new Thread(() -> DesktopIntegration.open(dir)).start();
|
||||
GlobalThreadPool.submit(() -> DesktopIntegration.open(dir));
|
||||
} else {
|
||||
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.util.concurrent.CompletableFuture;
|
||||
|
||||
import ctbrec.GlobalThreadPool;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.recorder.Recorder;
|
||||
import ctbrec.ui.controls.Dialogs;
|
||||
|
@ -36,6 +37,6 @@ public class RemoveTimeLimitAction {
|
|||
Dialogs.showError(source.getScene(), "Error", "Couln't remove stop date", e);
|
||||
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.LoggerFactory;
|
||||
|
||||
import ctbrec.GlobalThreadPool;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.SubsequentAction;
|
||||
import ctbrec.recorder.Recorder;
|
||||
|
@ -82,7 +83,7 @@ public class SetStopDateAction {
|
|||
}
|
||||
}
|
||||
return true;
|
||||
}).whenComplete((r, e) -> {
|
||||
}, GlobalThreadPool.get()).whenComplete((r, e) -> {
|
||||
source.setCursor(Cursor.DEFAULT);
|
||||
if (e != null) {
|
||||
LOG.error("Error", e);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package ctbrec.ui.action;
|
||||
|
||||
import ctbrec.GlobalThreadPool;
|
||||
import ctbrec.recorder.Recorder;
|
||||
import ctbrec.ui.controls.Dialogs;
|
||||
import javafx.application.Platform;
|
||||
|
@ -20,7 +21,7 @@ public class ToggleRecordingAction {
|
|||
|
||||
public void execute() {
|
||||
toggleButton.setCursor(Cursor.WAIT);
|
||||
Thread t = new Thread(() -> {
|
||||
GlobalThreadPool.submit(() -> {
|
||||
try {
|
||||
if (pause) {
|
||||
recorder.pause();
|
||||
|
@ -36,7 +37,5 @@ public class ToggleRecordingAction {
|
|||
Platform.runLater(() -> toggleButton.setCursor(Cursor.DEFAULT));
|
||||
}
|
||||
});
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,14 @@
|
|||
*/
|
||||
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.recorder.Recorder;
|
||||
import ctbrec.ui.action.PlayAction;
|
||||
|
@ -40,18 +48,15 @@ import javafx.event.EventHandler;
|
|||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Cursor;
|
||||
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.ImageView;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
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.
|
||||
|
@ -163,7 +168,7 @@ public class SearchPopoverTreeList extends PopoverTreeList<Model> implements Pop
|
|||
follow = new Button("Follow");
|
||||
follow.setOnAction(evt -> {
|
||||
setCursor(Cursor.WAIT);
|
||||
CompletableFuture.runAsync(new Task<Boolean>() {
|
||||
GlobalThreadPool.submit(new Task<Boolean>() {
|
||||
@Override
|
||||
protected Boolean call() throws Exception {
|
||||
model.getSite().login();
|
||||
|
@ -184,7 +189,7 @@ public class SearchPopoverTreeList extends PopoverTreeList<Model> implements Pop
|
|||
record = new Button("Record");
|
||||
record.setOnAction(evt -> {
|
||||
setCursor(Cursor.WAIT);
|
||||
CompletableFuture.runAsync(new Task<Void>() {
|
||||
GlobalThreadPool.submit(new Task<Void>() {
|
||||
@Override
|
||||
protected Void call() throws Exception {
|
||||
recorder.addModel(model);
|
||||
|
@ -292,12 +297,12 @@ public class SearchPopoverTreeList extends PopoverTreeList<Model> implements Pop
|
|||
|
||||
@Override
|
||||
protected double computePrefHeight(double width) {
|
||||
return thumbSize + 20;
|
||||
return thumbSize + 20.0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double computeMaxHeight(double width) {
|
||||
return thumbSize + 20;
|
||||
return thumbSize + 20.0;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -12,6 +12,7 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.GlobalThreadPool;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.io.HttpException;
|
||||
import ctbrec.recorder.download.StreamSource;
|
||||
|
@ -79,7 +80,7 @@ public class StreamPreview extends StackPane {
|
|||
double w = Config.getInstance().getSettings().thumbWidth;
|
||||
double h = w / aspect;
|
||||
resizeTo(w, h);
|
||||
} catch (Exception e) {}
|
||||
} catch (Exception e) { /* nothing to do */ }
|
||||
}
|
||||
|
||||
if(future != null && !future.isDone()) {
|
||||
|
@ -157,14 +158,14 @@ public class StreamPreview extends StackPane {
|
|||
running = false;
|
||||
MediaPlayer old = videoPlayer;
|
||||
Future<?> oldFuture = future;
|
||||
new Thread(() -> {
|
||||
GlobalThreadPool.submit(() -> {
|
||||
if(oldFuture != null && !oldFuture.isDone()) {
|
||||
oldFuture.cancel(true);
|
||||
}
|
||||
if(old != null) {
|
||||
old.dispose();
|
||||
}
|
||||
}).start();
|
||||
});
|
||||
}
|
||||
|
||||
private void onError(MediaPlayer videoPlayer) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package ctbrec.ui.controls;
|
||||
|
||||
import ctbrec.GlobalThreadPool;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
|
@ -13,6 +14,8 @@ import javafx.stage.StageStyle;
|
|||
import javafx.util.Duration;
|
||||
|
||||
public final class Toast {
|
||||
private Toast() {}
|
||||
|
||||
public static void makeText(Scene owner, String toastMsg, int toastDelay, int fadeInDelay, int fadeOutDelay) {
|
||||
Stage toastStage = new Stage();
|
||||
toastStage.initOwner(owner.getWindow());
|
||||
|
@ -35,20 +38,18 @@ public final class Toast {
|
|||
Timeline fadeInTimeline = new Timeline();
|
||||
KeyFrame fadeInKey1 = new KeyFrame(Duration.millis(fadeInDelay), new KeyValue(toastStage.getScene().getRoot().opacityProperty(), 1));
|
||||
fadeInTimeline.getKeyFrames().add(fadeInKey1);
|
||||
fadeInTimeline.setOnFinished((ae) -> {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Thread.sleep(toastDelay);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
Timeline fadeOutTimeline = new Timeline();
|
||||
KeyFrame fadeOutKey1 = new KeyFrame(Duration.millis(fadeOutDelay), new KeyValue(toastStage.getScene().getRoot().opacityProperty(), 0));
|
||||
fadeOutTimeline.getKeyFrames().add(fadeOutKey1);
|
||||
fadeOutTimeline.setOnFinished((aeb) -> toastStage.close());
|
||||
fadeOutTimeline.play();
|
||||
}).start();
|
||||
});
|
||||
fadeInTimeline.setOnFinished(ae -> GlobalThreadPool.submit(() -> {
|
||||
try {
|
||||
Thread.sleep(toastDelay);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
Timeline fadeOutTimeline = new Timeline();
|
||||
KeyFrame fadeOutKey1 = new KeyFrame(Duration.millis(fadeOutDelay), new KeyValue(toastStage.getScene().getRoot().opacityProperty(), 0));
|
||||
fadeOutTimeline.getKeyFrames().add(fadeOutKey1);
|
||||
fadeOutTimeline.setOnFinished(aeb -> toastStage.close());
|
||||
fadeOutTimeline.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;
|
||||
|
||||
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.Moshi;
|
||||
|
||||
import ctbrec.GlobalThreadPool;
|
||||
import ctbrec.io.HttpException;
|
||||
import ctbrec.ui.CamrecApplication;
|
||||
import ctbrec.ui.controls.Dialogs;
|
||||
|
@ -14,12 +23,6 @@ import javafx.scene.control.Tab;
|
|||
import javafx.scene.layout.VBox;
|
||||
import okhttp3.Request;
|
||||
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 {
|
||||
private static final String ACCESS_TOKEN = "a2804d73a89951a22e0f8483a6fcec8943afd88b7ba17c459c095aa9e6f94fd0";
|
||||
|
@ -36,7 +39,7 @@ public class NewsTab extends Tab implements TabSelectionListener {
|
|||
|
||||
@Override
|
||||
public void selected() {
|
||||
new Thread(this::loadToots).start();
|
||||
GlobalThreadPool.submit(this::loadToots);
|
||||
}
|
||||
|
||||
private void loadToots() {
|
||||
|
|
|
@ -9,13 +9,13 @@ import java.io.IOException;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.GlobalThreadPool;
|
||||
import ctbrec.Hmac;
|
||||
import ctbrec.Settings;
|
||||
import ctbrec.Settings.DirectoryStructure;
|
||||
|
@ -129,6 +129,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
private SimpleBooleanProperty transportLayerSecurity;
|
||||
private SimpleBooleanProperty fastScrollSpeed;
|
||||
private SimpleBooleanProperty useHlsdl;
|
||||
private SimpleBooleanProperty recentlyWatched;
|
||||
private SimpleFileProperty hlsdlExecutable;
|
||||
private ExclusiveSelectionProperty recordLocal;
|
||||
private SimpleIntegerProperty postProcessingThreads;
|
||||
|
@ -191,6 +192,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
confirmationDialogs = new SimpleBooleanProperty(null, "confirmationForDangerousActions", settings.confirmationForDangerousActions);
|
||||
useHlsdl = new SimpleBooleanProperty(null, "useHlsdl", settings.useHlsdl);
|
||||
hlsdlExecutable = new SimpleFileProperty(null, "hlsdlExecutable", settings.hlsdlExecutable);
|
||||
recentlyWatched = new SimpleBooleanProperty(null, "recentlyWatched", settings.recentlyWatched);
|
||||
}
|
||||
|
||||
private void createGui() {
|
||||
|
@ -214,6 +216,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
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("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("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"),
|
||||
|
@ -439,7 +442,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
}
|
||||
|
||||
public void saveConfig() {
|
||||
CompletableFuture.runAsync(() -> {
|
||||
GlobalThreadPool.submit(() -> {
|
||||
try {
|
||||
Config.getInstance().save();
|
||||
} catch (IOException e) {
|
||||
|
|
|
@ -22,7 +22,7 @@ import okhttp3.HttpUrl;
|
|||
|
||||
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 URL = BongaCams.baseUrl + "/login";
|
||||
private CookieJar cookieJar;
|
||||
|
@ -40,18 +40,18 @@ public class BongaCamsElectronLoginDialog {
|
|||
msg.put("config", config);
|
||||
browser.run(msg, msgHandler);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException("Couldn't wait for login dialog", e);
|
||||
} finally {
|
||||
browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
private Consumer<String> msgHandler = (line) -> {
|
||||
private Consumer<String> msgHandler = line -> {
|
||||
if(!line.startsWith("{")) {
|
||||
System.err.println(line);
|
||||
LOG.error("Didn't received a JSON object {}", line);
|
||||
} else {
|
||||
JSONObject json = new JSONObject(line);
|
||||
//LOG.debug("Browser: {}", json.toString(2));
|
||||
if(json.has("url")) {
|
||||
String url = json.getString("url");
|
||||
if(url.endsWith("/login")) {
|
||||
|
@ -82,16 +82,16 @@ public class BongaCamsElectronLoginDialog {
|
|||
}
|
||||
|
||||
if(json.has("cookies")) {
|
||||
JSONArray _cookies = json.getJSONArray("cookies");
|
||||
for (int i = 0; i < _cookies.length(); i++) {
|
||||
JSONObject cookie = _cookies.getJSONObject(i);
|
||||
JSONArray cookiesFromBrowser = json.getJSONArray("cookies");
|
||||
for (int i = 0; i < cookiesFromBrowser.length(); i++) {
|
||||
JSONObject cookie = cookiesFromBrowser.getJSONObject(i);
|
||||
if(cookie.getString("domain").contains(DOMAIN)) {
|
||||
Builder b = new Cookie.Builder()
|
||||
.path(cookie.getString("path"))
|
||||
.domain(DOMAIN)
|
||||
.name(cookie.getString("name"))
|
||||
.value(cookie.getString("value"))
|
||||
.expiresAt(Double.valueOf(cookie.optDouble("expirationDate")).longValue());
|
||||
.expiresAt((long) cookie.optDouble("expirationDate"));
|
||||
if(cookie.optBoolean("hostOnly")) {
|
||||
b.hostOnlyDomain(DOMAIN);
|
||||
}
|
||||
|
@ -108,8 +108,7 @@ public class BongaCamsElectronLoginDialog {
|
|||
}
|
||||
|
||||
try {
|
||||
URL _url = new URL(url);
|
||||
if (Objects.equals(_url.getPath(), "/")) {
|
||||
if (Objects.equals(new URL(url).getPath(), "/")) {
|
||||
browser.close();
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
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.BongaCamsHttpClient;
|
||||
import ctbrec.ui.controls.Dialogs;
|
||||
import ctbrec.ui.sites.AbstractSiteUi;
|
||||
import ctbrec.ui.sites.ConfigUI;
|
||||
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 {
|
||||
|
||||
|
@ -39,37 +38,20 @@ public class BongaCamsSiteUi extends AbstractSiteUi {
|
|||
@Override
|
||||
public synchronized boolean login() throws IOException {
|
||||
boolean automaticLogin = bongaCams.login();
|
||||
if(automaticLogin) {
|
||||
if (automaticLogin) {
|
||||
return true;
|
||||
} else {
|
||||
BlockingQueue<Boolean> queue = new LinkedBlockingQueue<>();
|
||||
// login with external browser window
|
||||
try {
|
||||
new Thread(() -> {
|
||||
// login with external browser window
|
||||
try {
|
||||
new BongaCamsElectronLoginDialog(bongaCams.getHttpClient().getCookieJar());
|
||||
} 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);
|
||||
new BongaCamsElectronLoginDialog(bongaCams.getHttpClient().getCookieJar());
|
||||
} catch (Exception e1) {
|
||||
LOG.error("Error logging in with external browser", e1);
|
||||
Dialogs.showError("Login error", "Couldn't login to " + bongaCams.getName(), e1);
|
||||
}
|
||||
|
||||
BongaCamsHttpClient httpClient = (BongaCamsHttpClient)bongaCams.getHttpClient();
|
||||
BongaCamsHttpClient httpClient = (BongaCamsHttpClient) bongaCams.getHttpClient();
|
||||
boolean loggedIn = httpClient.checkLoginSuccess();
|
||||
if(loggedIn) {
|
||||
if (loggedIn) {
|
||||
LOG.info("Logged in. User ID is {}", httpClient.getUserId());
|
||||
} else {
|
||||
LOG.info("Login failed");
|
||||
|
|
|
@ -22,7 +22,7 @@ import okhttp3.HttpUrl;
|
|||
|
||||
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 URL = Cam4.BASE_URI + "/login";
|
||||
private CookieJar cookieJar;
|
||||
|
@ -40,15 +40,16 @@ public class Cam4ElectronLoginDialog {
|
|||
msg.put("config", config);
|
||||
browser.run(msg, msgHandler);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException("Couldn't wait for login dialog", e);
|
||||
} finally {
|
||||
browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
private Consumer<String> msgHandler = (line) -> {
|
||||
private Consumer<String> msgHandler = line -> {
|
||||
if(!line.startsWith("{")) {
|
||||
System.err.println(line);
|
||||
LOG.error("Didn't received a JSON object {}", line);
|
||||
} else {
|
||||
JSONObject json = new JSONObject(line);
|
||||
if(json.has("url")) {
|
||||
|
@ -75,11 +76,10 @@ public class Cam4ElectronLoginDialog {
|
|||
}
|
||||
|
||||
if(json.has("cookies")) {
|
||||
JSONArray _cookies = json.getJSONArray("cookies");
|
||||
JSONArray cookiesFromBrowser = json.getJSONArray("cookies");
|
||||
try {
|
||||
URL _url = new URL(url);
|
||||
for (int i = 0; i < _cookies.length(); i++) {
|
||||
JSONObject cookie = _cookies.getJSONObject(i);
|
||||
for (int i = 0; i < cookiesFromBrowser.length(); i++) {
|
||||
JSONObject cookie = cookiesFromBrowser.getJSONObject(i);
|
||||
if(cookie.getString("domain").contains("cam4")) {
|
||||
String domain = cookie.getString("domain");
|
||||
if(domain.startsWith(".")) {
|
||||
|
@ -91,12 +91,8 @@ public class Cam4ElectronLoginDialog {
|
|||
cookieJar.saveFromResponse(HttpUrl.parse(Cam4.BASE_URI), Collections.singletonList(c));
|
||||
}
|
||||
}
|
||||
if (Objects.equals(_url.getPath(), "/")) {
|
||||
try {
|
||||
browser.close();
|
||||
} catch(IOException e) {
|
||||
LOG.error("Couldn't send close request to browser", e);
|
||||
}
|
||||
if (Objects.equals(new URL(url).getPath(), "/")) {
|
||||
closeBrowser();
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
LOG.error("Couldn't parse new url {}", url, e);
|
||||
|
@ -112,7 +108,7 @@ public class Cam4ElectronLoginDialog {
|
|||
.domain(domain)
|
||||
.name(cookie.getString("name"))
|
||||
.value(cookie.getString("value"))
|
||||
.expiresAt(Double.valueOf(cookie.optDouble("expirationDate")).longValue());
|
||||
.expiresAt((long) cookie.optDouble("expirationDate"));
|
||||
if(cookie.optBoolean("hostOnly")) {
|
||||
b.hostOnlyDomain(domain);
|
||||
}
|
||||
|
@ -124,4 +120,12 @@ public class Cam4ElectronLoginDialog {
|
|||
}
|
||||
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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -13,10 +11,9 @@ import ctbrec.ui.controls.Dialogs;
|
|||
import ctbrec.ui.sites.AbstractSiteUi;
|
||||
import ctbrec.ui.sites.ConfigUI;
|
||||
import ctbrec.ui.tabs.TabProvider;
|
||||
import javafx.application.Platform;
|
||||
|
||||
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 Cam4ConfigUI configUI;
|
||||
|
@ -44,33 +41,13 @@ public class Cam4SiteUi extends AbstractSiteUi {
|
|||
if (automaticLogin) {
|
||||
return true;
|
||||
} else {
|
||||
|
||||
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);
|
||||
// login with external browser
|
||||
try {
|
||||
queue.take();
|
||||
} catch (InterruptedException e) {
|
||||
LOG.error("Error while waiting for login dialog to close", e);
|
||||
throw new IOException(e);
|
||||
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);
|
||||
}
|
||||
|
||||
Cam4HttpClient httpClient = (Cam4HttpClient) cam4.getHttpClient();
|
||||
boolean loggedIn = httpClient.checkLoginSuccess();
|
||||
return loggedIn;
|
||||
|
|
|
@ -10,7 +10,6 @@ import java.util.List;
|
|||
import java.util.Locale;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.jsoup.nodes.Element;
|
||||
|
@ -43,14 +42,11 @@ public class Cam4UpdateService extends PaginatedScheduledService {
|
|||
this.url = url;
|
||||
this.loginRequired = loginRequired;
|
||||
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread t = new Thread(r);
|
||||
t.setDaemon(true);
|
||||
t.setName("ThumbOverviewTab UpdateService");
|
||||
return t;
|
||||
}
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor(r -> {
|
||||
Thread t = new Thread(r);
|
||||
t.setDaemon(true);
|
||||
t.setName("ThumbOverviewTab UpdateService");
|
||||
return t;
|
||||
});
|
||||
setExecutor(executor);
|
||||
}
|
||||
|
@ -60,16 +56,16 @@ public class Cam4UpdateService extends PaginatedScheduledService {
|
|||
return new Task<List<Model>>() {
|
||||
@Override
|
||||
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();
|
||||
} else {
|
||||
String url = Cam4UpdateService.this.url + "&page=" + page;
|
||||
LOG.debug("Fetching page {}", url);
|
||||
if(loginRequired) {
|
||||
String pageUrl = Cam4UpdateService.this.url + "&page=" + page;
|
||||
LOG.debug("Fetching page {}", pageUrl);
|
||||
if (loginRequired) {
|
||||
SiteUiFactory.getUi(site).login();
|
||||
}
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.url(pageUrl)
|
||||
.header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
|
||||
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
||||
.build();
|
||||
|
@ -91,7 +87,7 @@ public class Cam4UpdateService extends PaginatedScheduledService {
|
|||
model.setPreview("https://snapshots.xcdnpro.com/thumbnails/" + model.getName() + "?s=" + System.currentTimeMillis());
|
||||
model.setDescription(parseDesription(boxHtml));
|
||||
model.setOnlineState(ONLINE);
|
||||
if(boxHtml.contains("In private show")) {
|
||||
if (boxHtml.contains("In private show")) {
|
||||
model.setOnlineState(PRIVATE);
|
||||
}
|
||||
models.add(model);
|
||||
|
|
|
@ -1,32 +1,5 @@
|
|||
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.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
@ -40,7 +13,42 @@ import java.util.ArrayList;
|
|||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
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 {
|
||||
|
||||
|
@ -129,7 +137,7 @@ public class CamsodaShowsTab extends Tab implements TabSelectionListener {
|
|||
});
|
||||
}
|
||||
};
|
||||
CompletableFuture.runAsync(task);
|
||||
GlobalThreadPool.submit(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -173,17 +181,17 @@ public class CamsodaShowsTab extends Tab implements TabSelectionListener {
|
|||
grid.add(createLabel(formatter.format(endTime), false), 1, 1);
|
||||
Button record = new Button("Record Model");
|
||||
record.setTooltip(new Tooltip(record.getText()));
|
||||
record.setOnAction((evt) -> record(model));
|
||||
record.setOnAction(evt -> record(model));
|
||||
grid.add(record, 1, 2);
|
||||
GridPane.setMargin(record, new Insets(10));
|
||||
Button follow = new Button("Follow");
|
||||
follow.setTooltip(new Tooltip(follow.getText()));
|
||||
follow.setOnAction((evt) -> follow(model));
|
||||
follow.setOnAction(evt -> follow(model));
|
||||
grid.add(follow, 1, 3);
|
||||
GridPane.setMargin(follow, new Insets(10));
|
||||
Button openInBrowser = new Button("Open in Browser");
|
||||
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);
|
||||
GridPane.setMargin(openInBrowser, new Insets(10));
|
||||
root.setCenter(grid);
|
||||
|
@ -195,7 +203,7 @@ public class CamsodaShowsTab extends Tab implements TabSelectionListener {
|
|||
|
||||
private void follow(Model model) {
|
||||
setCursor(Cursor.WAIT);
|
||||
CompletableFuture.runAsync(() -> {
|
||||
GlobalThreadPool.submit(() -> {
|
||||
try {
|
||||
SiteUiFactory.getUi(model.getSite()).login();
|
||||
model.follow();
|
||||
|
@ -203,30 +211,26 @@ public class CamsodaShowsTab extends Tab implements TabSelectionListener {
|
|||
LOG.error("Couldn't follow model {}", model, e);
|
||||
showErrorDialog("Oh no!", "Couldn't follow model", e.getMessage());
|
||||
} finally {
|
||||
Platform.runLater(() -> {
|
||||
setCursor(Cursor.DEFAULT);
|
||||
});
|
||||
Platform.runLater(() -> setCursor(Cursor.DEFAULT));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void record(Model model) {
|
||||
setCursor(Cursor.WAIT);
|
||||
CompletableFuture.runAsync(() -> {
|
||||
GlobalThreadPool.submit(() -> {
|
||||
try {
|
||||
recorder.addModel(model);
|
||||
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) {
|
||||
showErrorDialog("Oh no!", "Couldn't add model to the recorder", "Recorder error: " + e.getMessage());
|
||||
} finally {
|
||||
Platform.runLater(() -> {
|
||||
setCursor(Cursor.DEFAULT);
|
||||
});
|
||||
Platform.runLater(() -> setCursor(Cursor.DEFAULT));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadImage(Model model, ImageView thumb) {
|
||||
CompletableFuture.runAsync(() -> {
|
||||
GlobalThreadPool.submit(() -> {
|
||||
try {
|
||||
String url = camsoda.getBaseUrl() + "/api/v1/user/" + model.getName();
|
||||
Request detailRequest = new Request.Builder().url(url).build();
|
||||
|
|
|
@ -5,7 +5,6 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -21,7 +20,7 @@ import okhttp3.Response;
|
|||
|
||||
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 boolean loginRequired;
|
||||
private Chaturbate chaturbate;
|
||||
|
@ -31,14 +30,11 @@ public class ChaturbateUpdateService extends PaginatedScheduledService {
|
|||
this.loginRequired = loginRequired;
|
||||
this.chaturbate = chaturbate;
|
||||
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread t = new Thread(r);
|
||||
t.setDaemon(true);
|
||||
t.setName("ThumbOverviewTab UpdateService");
|
||||
return t;
|
||||
}
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor(r -> {
|
||||
Thread t = new Thread(r);
|
||||
t.setDaemon(true);
|
||||
t.setName("ThumbOverviewTab UpdateService");
|
||||
return t;
|
||||
});
|
||||
setExecutor(executor);
|
||||
}
|
||||
|
@ -51,12 +47,12 @@ public class ChaturbateUpdateService extends PaginatedScheduledService {
|
|||
if(loginRequired && !chaturbate.credentialsAvailable()) {
|
||||
return Collections.emptyList();
|
||||
} else {
|
||||
String url = ChaturbateUpdateService.this.url + "?page="+page+"&keywords=&_=" + System.currentTimeMillis();
|
||||
LOG.debug("Fetching page {}", url);
|
||||
String pageUrl = ChaturbateUpdateService.this.url + "?page="+page+"&keywords=&_=" + System.currentTimeMillis();
|
||||
LOG.debug("Fetching page {}", pageUrl);
|
||||
if(loginRequired) {
|
||||
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);
|
||||
if (response.isSuccessful()) {
|
||||
List<Model> models = ChaturbateModelParser.parseModels(chaturbate, response.body().string());
|
||||
|
|
|
@ -5,6 +5,7 @@ import java.io.IOException;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ctbrec.GlobalThreadPool;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.sites.fc2live.Fc2Live;
|
||||
import ctbrec.sites.fc2live.Fc2Model;
|
||||
|
@ -16,7 +17,7 @@ import ctbrec.ui.sites.ConfigUI;
|
|||
import ctbrec.ui.tabs.TabProvider;
|
||||
|
||||
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 Fc2TabProvider tabProvider;
|
||||
private Fc2LiveConfigUI configUi;
|
||||
|
@ -44,10 +45,10 @@ public class Fc2LiveSiteUi extends AbstractSiteUi {
|
|||
|
||||
@Override
|
||||
public boolean play(Model model) {
|
||||
new Thread(() -> {
|
||||
GlobalThreadPool.submit(() -> {
|
||||
Fc2Model m;
|
||||
if(model instanceof JavaFxModel) {
|
||||
m = (Fc2Model) ((JavaFxModel)model).getDelegate();
|
||||
if (model instanceof JavaFxModel) {
|
||||
m = (Fc2Model) ((JavaFxModel) model).getDelegate();
|
||||
} else {
|
||||
m = (Fc2Model) model;
|
||||
}
|
||||
|
@ -55,12 +56,20 @@ public class Fc2LiveSiteUi extends AbstractSiteUi {
|
|||
m.openWebsocket();
|
||||
LOG.debug("Starting player for {}", model);
|
||||
Player.play(model, false);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
handleException(e);
|
||||
} catch (IOException e) {
|
||||
handleException(e);
|
||||
} finally {
|
||||
m.closeWebsocket();
|
||||
} catch (InterruptedException | IOException e) {
|
||||
LOG.error("Error playing the stream", e);
|
||||
Dialogs.showError("Player", "Error playing the stream", e);
|
||||
}
|
||||
}).start();
|
||||
});
|
||||
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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
@ -17,7 +15,7 @@ import ctbrec.ui.tabs.TabProvider;
|
|||
|
||||
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 LiveJasminTabProvider tabProvider;
|
||||
private LiveJasminConfigUi configUi;
|
||||
|
@ -44,43 +42,27 @@ public class LiveJasminSiteUi extends AbstractSiteUi {
|
|||
// renew login every 30 min
|
||||
long now = System.currentTimeMillis();
|
||||
boolean renew = false;
|
||||
if((now - lastLoginTime) > TimeUnit.MINUTES.toMillis(30)) {
|
||||
if ((now - lastLoginTime) > TimeUnit.MINUTES.toMillis(30)) {
|
||||
renew = true;
|
||||
}
|
||||
|
||||
boolean automaticLogin = liveJasmin.login();
|
||||
if(automaticLogin && !renew) {
|
||||
if (automaticLogin && !renew) {
|
||||
return true;
|
||||
} else {
|
||||
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 {
|
||||
queue.take();
|
||||
} catch (InterruptedException e) {
|
||||
LOG.error("Error while waiting for login dialog to close", e);
|
||||
throw new IOException(e);
|
||||
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);
|
||||
}
|
||||
|
||||
LiveJasminHttpClient httpClient = (LiveJasminHttpClient)liveJasmin.getHttpClient();
|
||||
LiveJasminHttpClient httpClient = (LiveJasminHttpClient) liveJasmin.getHttpClient();
|
||||
boolean loggedIn = httpClient.checkLoginSuccess();
|
||||
if(loggedIn) {
|
||||
if (loggedIn) {
|
||||
LOG.info("Logged in");
|
||||
} else {
|
||||
LOG.info("Login failed");
|
||||
|
|
|
@ -26,19 +26,16 @@ public class MyFreeCamsTabProvider extends TabProvider {
|
|||
List<Tab> tabs = new ArrayList<>();
|
||||
|
||||
PaginatedScheduledService updateService = new OnlineCamsUpdateService();
|
||||
ThumbOverviewTab online = new ThumbOverviewTab("Online", updateService, myFreeCams);
|
||||
online.setRecorder(recorder);
|
||||
tabs.add(online);
|
||||
tabs.add(createTab("Online", updateService));
|
||||
|
||||
friends = new MyFreeCamsFriendsTab(myFreeCams);
|
||||
friends.setRecorder(recorder);
|
||||
friends.setImageAspectRatio(9.0 / 16.0);
|
||||
friends.preserveAspectRatioProperty().set(false);
|
||||
tabs.add(friends);
|
||||
|
||||
updateService = new HDCamsUpdateService();
|
||||
ThumbOverviewTab hd = createTab("HD", updateService);
|
||||
hd.setImageAspectRatio(9.0 / 16.0);
|
||||
hd.preserveAspectRatioProperty().set(false);
|
||||
tabs.add(hd);
|
||||
tabs.add(createTab("HD", updateService));
|
||||
|
||||
updateService = new PopularModelService();
|
||||
tabs.add(createTab("Most Popular", updateService));
|
||||
|
@ -54,6 +51,8 @@ public class MyFreeCamsTabProvider extends TabProvider {
|
|||
|
||||
private ThumbOverviewTab createTab(String title, PaginatedScheduledService updateService) {
|
||||
ThumbOverviewTab tab = new ThumbOverviewTab(title, updateService, myFreeCams);
|
||||
tab.setImageAspectRatio(9.0 / 16.0);
|
||||
tab.preserveAspectRatioProperty().set(false);
|
||||
tab.setRecorder(recorder);
|
||||
return tab;
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import com.iheartradio.m3u8.ParseException;
|
|||
import com.iheartradio.m3u8.PlaylistException;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.GlobalThreadPool;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.Settings;
|
||||
import ctbrec.StringUtil;
|
||||
|
@ -353,7 +354,7 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener {
|
|||
|
||||
if (Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
|
||||
MenuItem debug = new MenuItem("debug");
|
||||
debug.setOnAction(e -> new Thread(() -> {
|
||||
debug.setOnAction(e -> GlobalThreadPool.submit(() -> {
|
||||
for (Model m : selectedModels) {
|
||||
try {
|
||||
List<StreamSource> sources = m.getStreamSources();
|
||||
|
@ -365,7 +366,7 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener {
|
|||
LOG.error("Couldn't get stream sources", e1);
|
||||
}
|
||||
}
|
||||
}).start());
|
||||
}));
|
||||
menu.getItems().add(debug);
|
||||
}
|
||||
|
||||
|
@ -464,7 +465,7 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener {
|
|||
|
||||
private String escape(Property<?> prop) {
|
||||
String value = prop.getValue() != null ? prop.getValue().toString() : "";
|
||||
return "\"" + value.replaceAll("\"", "\"\"") + "\"";
|
||||
return "\"" + value.replace("\"", "\"\"") + "\"";
|
||||
}
|
||||
|
||||
private void showColumnSelection(ActionEvent evt) {
|
||||
|
@ -566,7 +567,7 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener {
|
|||
if(!file.exists()) {
|
||||
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);
|
||||
for (int i = 0; i < data.length(); i++) {
|
||||
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(occupation, Optional.ofNullable(st.getU()).map(User::getOccupation));
|
||||
int flags = Optional.ofNullable(st.getM()).map(ctbrec.sites.mfc.Model::getFlags).orElse(0);
|
||||
//isHd.set((flags & 1024) == 1024);
|
||||
isWebrtc.set((flags & 524288) == 524288);
|
||||
isHd.set(Optional.ofNullable(st.getU()).map(User::getPhase).orElse("z").equalsIgnoreCase("a"));
|
||||
flagsProperty.setValue(flags);
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package ctbrec.ui.sites.showup;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -44,33 +42,17 @@ public class ShowupSiteUi extends AbstractSiteUi {
|
|||
if (automaticLogin) {
|
||||
return true;
|
||||
} else {
|
||||
BlockingQueue<Boolean> queue = new LinkedBlockingQueue<>();
|
||||
// login with external browser window
|
||||
try {
|
||||
new Thread(() -> {
|
||||
// login with external browser window
|
||||
try {
|
||||
new ShowupElectronLoginDialog(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);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}).start();
|
||||
queue.take();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException(e);
|
||||
new ShowupElectronLoginDialog(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);
|
||||
}
|
||||
|
||||
ShowupHttpClient httpClient = (ShowupHttpClient)site.getHttpClient();
|
||||
ShowupHttpClient httpClient = (ShowupHttpClient) site.getHttpClient();
|
||||
boolean loggedIn = httpClient.checkLoginSuccess();
|
||||
if(loggedIn) {
|
||||
if (loggedIn) {
|
||||
LOG.info("Logged in");
|
||||
} else {
|
||||
LOG.info("Login failed");
|
||||
|
@ -78,5 +60,4 @@ public class ShowupSiteUi extends AbstractSiteUi {
|
|||
return loggedIn;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package ctbrec.ui.sites.stripchat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -13,7 +11,6 @@ import ctbrec.ui.controls.Dialogs;
|
|||
import ctbrec.ui.sites.AbstractSiteUi;
|
||||
import ctbrec.ui.sites.ConfigUI;
|
||||
import ctbrec.ui.tabs.TabProvider;
|
||||
import javafx.application.Platform;
|
||||
|
||||
public class StripchatSiteUi extends AbstractSiteUi {
|
||||
|
||||
|
@ -45,31 +42,12 @@ public class StripchatSiteUi extends AbstractSiteUi {
|
|||
if (automaticLogin) {
|
||||
return true;
|
||||
} else {
|
||||
|
||||
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);
|
||||
// login with external browser
|
||||
try {
|
||||
queue.take();
|
||||
} catch (InterruptedException e) {
|
||||
LOG.error("Error while waiting for login dialog to close", e);
|
||||
throw new IOException(e);
|
||||
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);
|
||||
}
|
||||
|
||||
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.sites.Site;
|
||||
import ctbrec.ui.ShutdownListener;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TabPane;
|
||||
|
||||
public class RecordedTab extends Tab implements TabSelectionListener {
|
||||
public class RecordedTab extends Tab implements TabSelectionListener, ShutdownListener {
|
||||
|
||||
private TabPane tabPane;
|
||||
private RecordedModelsTab recordedModelsTab;
|
||||
|
@ -54,7 +55,8 @@ public class RecordedTab extends Tab implements TabSelectionListener {
|
|||
}
|
||||
}
|
||||
|
||||
public void saveState() {
|
||||
@Override
|
||||
public void onShutdown() {
|
||||
recordedModelsTab.saveState();
|
||||
recordLaterTab.saveState();
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.GlobalThreadPool;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.Recording;
|
||||
import ctbrec.Recording.State;
|
||||
|
@ -45,6 +46,7 @@ import ctbrec.ui.DesktopIntegration;
|
|||
import ctbrec.ui.FileDownload;
|
||||
import ctbrec.ui.JavaFxRecording;
|
||||
import ctbrec.ui.Player;
|
||||
import ctbrec.ui.ShutdownListener;
|
||||
import ctbrec.ui.action.FollowAction;
|
||||
import ctbrec.ui.action.PauseAction;
|
||||
import ctbrec.ui.action.PlayAction;
|
||||
|
@ -90,7 +92,7 @@ import javafx.scene.text.Font;
|
|||
import javafx.stage.FileChooser;
|
||||
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 Logger LOG = LoggerFactory.getLogger(RecordingsTab.class);
|
||||
|
@ -496,9 +498,9 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
|||
|
||||
private void openContactSheet(JavaFxRecording recording) {
|
||||
if (config.getSettings().localRecording) {
|
||||
recording.getContactSheet().ifPresent(f -> new Thread(() -> DesktopIntegration.open(f)).start());
|
||||
recording.getContactSheet().ifPresent(f -> GlobalThreadPool.submit(() -> DesktopIntegration.open(f)));
|
||||
} else {
|
||||
recording.getContactSheet().ifPresent(f -> new Thread(() -> {
|
||||
recording.getContactSheet().ifPresent(f -> GlobalThreadPool.submit(() -> {
|
||||
File target;
|
||||
try {
|
||||
target = File.createTempFile("cs_", ".jpg");
|
||||
|
@ -516,7 +518,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
|||
} catch (IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException 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);
|
||||
if (newNote.isPresent()) {
|
||||
table.setCursor(Cursor.WAIT);
|
||||
Thread backgroundThread = new Thread(() -> {
|
||||
GlobalThreadPool.submit(() -> {
|
||||
List<Exception> exceptions = new ArrayList<>();
|
||||
try {
|
||||
recording.setNote(newNote.get());
|
||||
|
@ -542,13 +544,12 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
|||
});
|
||||
}
|
||||
});
|
||||
backgroundThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
private void pin(List<JavaFxRecording> recordings) {
|
||||
table.setCursor(Cursor.WAIT);
|
||||
Thread backgroundThread = new Thread(() -> {
|
||||
GlobalThreadPool.submit(() -> {
|
||||
List<Exception> exceptions = new ArrayList<>();
|
||||
try {
|
||||
for (JavaFxRecording javaFxRecording : recordings) {
|
||||
|
@ -569,12 +570,11 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
|||
});
|
||||
}
|
||||
});
|
||||
backgroundThread.start();
|
||||
}
|
||||
|
||||
private void unpin(List<JavaFxRecording> recordings) {
|
||||
table.setCursor(Cursor.WAIT);
|
||||
Thread backgroundThread = new Thread(() -> {
|
||||
GlobalThreadPool.submit(() -> {
|
||||
List<Exception> exceptions = new ArrayList<>();
|
||||
try {
|
||||
for (JavaFxRecording javaFxRecording : recordings) {
|
||||
|
@ -595,7 +595,6 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
|||
});
|
||||
}
|
||||
});
|
||||
backgroundThread.start();
|
||||
}
|
||||
|
||||
private void jumpToNextModel(KeyCode code) {
|
||||
|
@ -646,11 +645,11 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
|||
|
||||
private void onOpenDirectory(JavaFxRecording first) {
|
||||
File tsFile = first.getAbsoluteFile();
|
||||
new Thread(() -> DesktopIntegration.open(tsFile.getParent())).start();
|
||||
GlobalThreadPool.submit(() -> DesktopIntegration.open(tsFile.getParent()));
|
||||
}
|
||||
|
||||
private void triggerPostProcessing(List<JavaFxRecording> recs) {
|
||||
new Thread(() -> {
|
||||
GlobalThreadPool.submit(() -> {
|
||||
for (JavaFxRecording rec : recs) {
|
||||
try {
|
||||
recorder.rerunPostProcessing(rec.getDelegate());
|
||||
|
@ -659,7 +658,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
|||
LOG.error("Error while starting post-processing", e1);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
});
|
||||
}
|
||||
|
||||
private void download(Recording recording) {
|
||||
|
@ -761,12 +760,12 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
|||
}
|
||||
|
||||
private void play(Recording recording) {
|
||||
new Thread(() -> {
|
||||
GlobalThreadPool.submit(() -> {
|
||||
boolean started = Player.play(recording);
|
||||
if (started && Config.getInstance().getSettings().showPlayerStarting) {
|
||||
Platform.runLater(() -> Toast.makeText(getTabPane().getScene(), "Starting Player", 2000, 500, 500));
|
||||
}
|
||||
}).start();
|
||||
});
|
||||
}
|
||||
|
||||
private void play(Model model) {
|
||||
|
@ -795,7 +794,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
|||
}
|
||||
|
||||
private void deleteAsync(List<JavaFxRecording> recordings) {
|
||||
Thread deleteThread = new Thread(() -> {
|
||||
GlobalThreadPool.submit(() -> {
|
||||
recordingsLock.lock();
|
||||
try {
|
||||
List<Recording> deleted = new ArrayList<>();
|
||||
|
@ -821,10 +820,10 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
|||
Platform.runLater(() -> table.setCursor(Cursor.DEFAULT));
|
||||
}
|
||||
});
|
||||
deleteThread.start();
|
||||
}
|
||||
|
||||
public void saveState() {
|
||||
@Override
|
||||
public void onShutdown() {
|
||||
if (!table.getSortOrder().isEmpty()) {
|
||||
TableColumn<JavaFxRecording, ?> col = table.getSortOrder().get(0);
|
||||
Config.getInstance().getSettings().recordingsSortColumn = col.getText();
|
||||
|
|
|
@ -8,8 +8,6 @@ import java.util.Locale;
|
|||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
|
@ -21,6 +19,7 @@ import com.google.common.cache.CacheLoader;
|
|||
import com.google.common.cache.LoadingCache;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.GlobalThreadPool;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.Model.State;
|
||||
import ctbrec.io.HttpException;
|
||||
|
@ -106,7 +105,6 @@ public class ThumbCell extends StackPane {
|
|||
private ObservableList<Node> thumbCellList;
|
||||
private boolean mouseHovering = false;
|
||||
private boolean recording = false;
|
||||
private static ExecutorService imageLoadingThreadPool = Executors.newFixedThreadPool(30);
|
||||
static LoadingCache<Model, int[]> resolutionCache = CacheBuilder.newBuilder()
|
||||
.expireAfterAccess(5, TimeUnit.MINUTES)
|
||||
.maximumSize(10000)
|
||||
|
@ -285,7 +283,7 @@ public class ThumbCell extends StackPane {
|
|||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
}).whenComplete((result, exception) -> {
|
||||
}, GlobalThreadPool.get()).whenComplete((result, exception) -> {
|
||||
startPreview = null;
|
||||
if (result.booleanValue()) {
|
||||
setPreviewVisible(previewTrigger, true);
|
||||
|
@ -387,7 +385,7 @@ public class ThumbCell extends StackPane {
|
|||
if (!Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
|
||||
boolean updateThumbs = Config.getInstance().getSettings().updateThumbnails;
|
||||
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) {
|
||||
setCursor(Cursor.WAIT);
|
||||
new Thread(() -> {
|
||||
GlobalThreadPool.submit(() -> {
|
||||
try {
|
||||
if (pause) {
|
||||
recorder.suspendRecording(model);
|
||||
|
@ -532,11 +530,11 @@ public class ThumbCell extends StackPane {
|
|||
} finally {
|
||||
Platform.runLater(() -> setCursor(Cursor.DEFAULT));
|
||||
}
|
||||
}).start();
|
||||
});
|
||||
}
|
||||
|
||||
private void startStopActionAsync(Model model, boolean start) {
|
||||
new Thread(() -> {
|
||||
GlobalThreadPool.submit(() -> {
|
||||
try {
|
||||
if (start) {
|
||||
recorder.addModel(model);
|
||||
|
@ -552,7 +550,7 @@ public class ThumbCell extends StackPane {
|
|||
} finally {
|
||||
Platform.runLater(() -> setCursor(Cursor.DEFAULT));
|
||||
}
|
||||
}).start();
|
||||
});
|
||||
}
|
||||
|
||||
CompletableFuture<Boolean> follow(boolean follow) {
|
||||
|
@ -587,7 +585,7 @@ public class ThumbCell extends StackPane {
|
|||
} finally {
|
||||
Platform.runLater(() -> setCursor(Cursor.DEFAULT));
|
||||
}
|
||||
});
|
||||
}, GlobalThreadPool.get());
|
||||
}
|
||||
|
||||
void recordLater(boolean recordLater) {
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.GlobalThreadPool;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.event.EventBusHolder;
|
||||
import ctbrec.recorder.Recorder;
|
||||
|
@ -299,7 +300,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
|
|||
return;
|
||||
}
|
||||
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) {
|
||||
MenuItem sendTip = new MenuItem("Send Tip");
|
||||
sendTip.setOnAction(e -> {
|
||||
TipDialog tipDialog = new TipDialog(getTabPane().getScene(), site, cell.getModel());
|
||||
TipDialog tipDialog = new TipDialog(getTabPane().getScene(), site);
|
||||
tipDialog.showAndWait();
|
||||
String tipText = tipDialog.getResult();
|
||||
if(tipText != null) {
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
package ctbrec.ui.tabs;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ctbrec.GlobalThreadPool;
|
||||
import ctbrec.io.HttpException;
|
||||
import ctbrec.ui.CamrecApplication;
|
||||
import ctbrec.ui.CamrecApplication.Release;
|
||||
|
@ -47,7 +46,7 @@ public class UpdateTab extends Tab implements TabSelectionListener {
|
|||
}
|
||||
|
||||
public void loadChangeLog() {
|
||||
CompletableFuture.runAsync(() -> {
|
||||
GlobalThreadPool.submit(() -> {
|
||||
Request req = new Request.Builder().url("https://pastebin.com/raw/fiAPtM0s").build();
|
||||
try (Response resp = CamrecApplication.httpClient.execute(req)) {
|
||||
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 java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
@ -14,6 +15,9 @@ import java.util.Optional;
|
|||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ctbrec.event.EventBusHolder;
|
||||
import ctbrec.event.RecordingStateChangedEvent;
|
||||
import ctbrec.io.IoUtils;
|
||||
|
@ -21,6 +25,8 @@ import ctbrec.recorder.download.Download;
|
|||
import ctbrec.recorder.download.VideoLengthDetector;
|
||||
|
||||
public class Recording implements Serializable, Callable<Recording> {
|
||||
private static final transient Logger LOG = LoggerFactory.getLogger(Recording.class);
|
||||
|
||||
private String id;
|
||||
private Model model;
|
||||
private transient Download download;
|
||||
|
@ -104,10 +110,6 @@ public class Recording implements Serializable, Callable<Recording> {
|
|||
this.progress = progress;
|
||||
}
|
||||
|
||||
// public String getPath() {
|
||||
// return path;
|
||||
// }
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
@ -246,22 +248,43 @@ public class Recording implements Serializable, Callable<Recording> {
|
|||
}
|
||||
|
||||
private long getSize() {
|
||||
File rec = getAbsoluteFile();
|
||||
if (rec.isDirectory()) {
|
||||
return IoUtils.getDirectorySize(rec);
|
||||
} else {
|
||||
if (!rec.exists()) {
|
||||
if (rec.getName().endsWith(".m3u8")) {
|
||||
return IoUtils.getDirectorySize(rec.getParentFile());
|
||||
try {
|
||||
Set<File> files = getAllRecordingFiles();
|
||||
long sum = 0;
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
sum += IoUtils.getDirectorySize(file);
|
||||
} 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() {
|
||||
sizeInByte = getSize();
|
||||
}
|
||||
|
|
|
@ -120,6 +120,7 @@ public class Settings {
|
|||
public String proxyPort;
|
||||
public ProxyType proxyType = ProxyType.DIRECT;
|
||||
public String proxyUser;
|
||||
public boolean recentlyWatched = true;
|
||||
public double[] recordLaterColumnWidths = new double[0];
|
||||
public String[] recordLaterColumnIds = new String[0];
|
||||
public String recordLaterSortColumn = "";
|
||||
|
|
|
@ -1,15 +1,7 @@
|
|||
package ctbrec.io;
|
||||
|
||||
import com.squareup.moshi.JsonAdapter;
|
||||
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 static java.nio.charset.StandardCharsets.*;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
@ -21,11 +13,40 @@ import java.security.NoSuchAlgorithmException;
|
|||
import java.security.SecureRandom;
|
||||
import java.security.cert.CertificateException;
|
||||
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.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
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 {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HttpClient.class);
|
||||
|
@ -275,10 +296,22 @@ public abstract class HttpClient {
|
|||
}
|
||||
|
||||
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) {
|
||||
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.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
@ -216,6 +215,7 @@ public class NextGenLocalRecorder implements Recorder {
|
|||
break;
|
||||
}
|
||||
}
|
||||
recording.refresh();
|
||||
if (recording.getStatus() != State.DELETED) {
|
||||
setRecordingStatus(recording, State.FINISHED);
|
||||
recordingManager.saveRecording(recording);
|
||||
|
@ -448,58 +448,40 @@ public class NextGenLocalRecorder implements Recorder {
|
|||
recording = false;
|
||||
|
||||
if (!immediately) {
|
||||
LOG.debug("Stopping all recording processes");
|
||||
recorderLock.lock();
|
||||
try {
|
||||
// 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();
|
||||
}
|
||||
stopRecordingProcesses();
|
||||
awaitDownloadsFinish();
|
||||
shutdownThreadPools();
|
||||
}
|
||||
}
|
||||
|
||||
// wait for downloads to finish
|
||||
LOG.info("Waiting for downloads to finish");
|
||||
for (int i = 0; i < 60; i++) {
|
||||
if (!recordingProcesses.isEmpty()) {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
LOG.error("Error while waiting for downloads to finish", e);
|
||||
}
|
||||
private void awaitDownloadsFinish() {
|
||||
LOG.info("Waiting for downloads to finish");
|
||||
for (int i = 0; i < 60; i++) {
|
||||
if (!recordingProcesses.isEmpty()) {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
LOG.error("Error while waiting for downloads to finish", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// shutdown threadpools
|
||||
try {
|
||||
LOG.info("Shutting down download pool");
|
||||
downloadPool.shutdown();
|
||||
client.shutdown();
|
||||
downloadPool.awaitTermination(1, TimeUnit.MINUTES);
|
||||
LOG.info("Shutting down post-processing pool");
|
||||
ppPool.shutdown();
|
||||
int minutesToWait = 10;
|
||||
LOG.info("Waiting {} minutes (max) for post-processing to finish", minutesToWait);
|
||||
ppPool.awaitTermination(minutesToWait, TimeUnit.MINUTES);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
LOG.error("Error while waiting for pools to finish", e);
|
||||
}
|
||||
private void shutdownThreadPools() {
|
||||
try {
|
||||
LOG.info("Shutting down download pool");
|
||||
downloadPool.shutdown();
|
||||
client.shutdown();
|
||||
downloadPool.awaitTermination(1, TimeUnit.MINUTES);
|
||||
LOG.info("Shutting down post-processing pool");
|
||||
ppPool.shutdown();
|
||||
int minutesToWait = 10;
|
||||
LOG.info("Waiting {} minutes (max) for post-processing to finish", minutesToWait);
|
||||
ppPool.awaitTermination(minutesToWait, TimeUnit.MINUTES);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
LOG.error("Error while waiting for pools to finish", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -703,7 +685,6 @@ public class NextGenLocalRecorder implements Recorder {
|
|||
config.save();
|
||||
} else {
|
||||
LOG.warn("Couldn't change priority for model {}. Not found in list", model.getName());
|
||||
return;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Couldn't save config", e);
|
||||
|
|
|
@ -272,7 +272,6 @@ public class DashDownload extends AbstractDownload {
|
|||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private boolean splitRecording() {
|
||||
if (splittingStrategy.splitNecessary(this)) {
|
||||
internalStop();
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package ctbrec.recorder.download.hls;
|
||||
|
||||
import static ctbrec.recorder.download.StreamSource.*;
|
||||
import static java.util.concurrent.TimeUnit.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
@ -41,6 +43,8 @@ public class HlsdlDownload extends AbstractDownload {
|
|||
private transient Hlsdl hlsdl;
|
||||
protected transient Process hlsdlProcess;
|
||||
protected transient boolean running = true;
|
||||
protected transient Instant lastSizeChange = Instant.now();
|
||||
protected transient long lastSize = 0;
|
||||
|
||||
@Override
|
||||
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)) {
|
||||
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) {
|
||||
LOG.error("hlsdl exited unclean", e);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,18 @@
|
|||
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.Model;
|
||||
import ctbrec.StringUtil;
|
||||
|
@ -8,18 +21,6 @@ import ctbrec.io.HttpException;
|
|||
import ctbrec.sites.AbstractSite;
|
||||
import okhttp3.Request;
|
||||
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 {
|
||||
|
||||
|
@ -121,27 +122,27 @@ public class Cam4 extends AbstractSite {
|
|||
|
||||
private void search(String q, boolean offline, List<Model> models) throws IOException {
|
||||
String url = BASE_URI + "/usernameSearch?username=" + URLEncoder.encode(q, "utf-8");
|
||||
if(offline) {
|
||||
if (offline) {
|
||||
url += "&offline=true";
|
||||
}
|
||||
Request req = new Request.Builder()
|
||||
.url(url)
|
||||
.addHeader(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
||||
.build();
|
||||
try(Response response = getHttpClient().execute(req)) {
|
||||
if(response.isSuccessful()) {
|
||||
String body = bodyToJsonObject(response);
|
||||
try (Response response = getHttpClient().execute(req)) {
|
||||
if (response.isSuccessful()) {
|
||||
String body = bodyToJsonArray(response);
|
||||
JSONArray results = new JSONArray(body);
|
||||
for (int i = 0; i < results.length(); i++) {
|
||||
JSONObject result = results.getJSONObject(i);
|
||||
Model model = createModel(result.getString("username"));
|
||||
String thumb = null;
|
||||
if(result.has("thumbnailId")) {
|
||||
if (result.has("thumbnailId")) {
|
||||
thumb = "https://snapshots.xcdnpro.com/thumbnails/" + model.getName() + "?s=" + result.getString("thumbnailId");
|
||||
} else {
|
||||
thumb = result.getString("profileImageLink");
|
||||
}
|
||||
if(StringUtil.isNotBlank(thumb)) {
|
||||
if (StringUtil.isNotBlank(thumb)) {
|
||||
model.setPreview(thumb);
|
||||
}
|
||||
models.add(model);
|
||||
|
|
|
@ -191,9 +191,13 @@ public class Cam4Model extends AbstractModel {
|
|||
StreamSource src = new StreamSource();
|
||||
src.bandwidth = playlist.getStreamInfo().getBandwidth();
|
||||
src.height = Optional.ofNullable(playlist.getStreamInfo()).map(StreamInfo::getResolution).map(res -> res.height).orElse(0);
|
||||
String masterUrl = getPlaylistUrl();
|
||||
String baseUrl = masterUrl.substring(0, masterUrl.lastIndexOf('/') + 1);
|
||||
src.mediaPlaylistUrl = baseUrl + playlist.getUri();
|
||||
if (playlist.getUri().startsWith("http")) {
|
||||
src.mediaPlaylistUrl = 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);
|
||||
sources.add(src);
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.GlobalThreadPool;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.io.HttpClient;
|
||||
import okhttp3.Request;
|
||||
|
@ -78,9 +79,9 @@ public class LiveJasminTippingWebSocket {
|
|||
LOG.trace("relay <-- {} T{}", model.getName(), text);
|
||||
JSONObject event = new JSONObject(text);
|
||||
if (event.optString("event").equals("accept")) {
|
||||
new Thread(() -> {
|
||||
GlobalThreadPool.submit(() -> {
|
||||
sendToRelay("{\"event\":\"connectSharedObject\",\"name\":\"data/chat_so\"}");
|
||||
}).start();
|
||||
});
|
||||
} else if(event.optString("event").equals("call")) {
|
||||
String func = event.optString("funcName");
|
||||
if (func.equals("setName")) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package ctbrec.recorder.server;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.*;
|
||||
import static javax.servlet.http.HttpServletResponse.*;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
|
@ -53,6 +54,7 @@ import org.slf4j.LoggerFactory;
|
|||
import com.google.common.base.Objects;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.NotLoggedInExcetion;
|
||||
import ctbrec.Version;
|
||||
import ctbrec.event.EventBusHolder;
|
||||
import ctbrec.event.EventHandler;
|
||||
|
@ -124,7 +126,7 @@ public class HttpServer {
|
|||
if (success) {
|
||||
LOG.info("Successfully logged in to {}", site.getName());
|
||||
} else {
|
||||
throw new RuntimeException("Login returned false");
|
||||
throw new NotLoggedInExcetion("Login returned false");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
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]);
|
||||
try {
|
||||
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());
|
||||
} catch (Exception e) {
|
||||
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||
|
|
|
@ -1,23 +1,7 @@
|
|||
package ctbrec.recorder.server;
|
||||
|
||||
import com.squareup.moshi.JsonAdapter;
|
||||
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 static javax.servlet.http.HttpServletResponse.*;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidKeyException;
|
||||
|
@ -27,7 +11,27 @@ import java.util.Iterator;
|
|||
import java.util.List;
|
||||
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 {
|
||||
|
||||
|
@ -87,24 +91,24 @@ public class RecorderServlet extends AbstractCtbrecServlet {
|
|||
resp.getWriter().write(response);
|
||||
break;
|
||||
case "stop":
|
||||
new Thread(() -> {
|
||||
GlobalThreadPool.submit(() -> {
|
||||
try {
|
||||
recorder.stopRecording(request.model);
|
||||
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) {
|
||||
LOG.error("Couldn't stop recording for model {}", request.model, e);
|
||||
}
|
||||
}).start();
|
||||
});
|
||||
response = "{\"status\": \"success\", \"msg\": \"Stopping recording\"}";
|
||||
resp.getWriter().write(response);
|
||||
break;
|
||||
case "stopAt":
|
||||
new Thread(() -> {
|
||||
GlobalThreadPool.submit(() -> {
|
||||
try {
|
||||
recorder.stopRecordingAt(request.model);
|
||||
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) {
|
||||
LOG.error("Couldn't stop recording for model {}", request.model, e);
|
||||
}
|
||||
}).start();
|
||||
});
|
||||
response = "{\"status\": \"success\", \"msg\": \"Stopping recording\"}";
|
||||
resp.getWriter().write(response);
|
||||
break;
|
||||
|
@ -189,13 +193,13 @@ public class RecorderServlet extends AbstractCtbrecServlet {
|
|||
break;
|
||||
case "suspend":
|
||||
LOG.debug("Suspend recording for model {} - {}", request.model.getName(), request.model.getUrl());
|
||||
new Thread(() -> {
|
||||
GlobalThreadPool.submit(() -> {
|
||||
try {
|
||||
recorder.suspendRecording(request.model);
|
||||
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) {
|
||||
LOG.error("Couldn't suspend recording for model {}", request.model, e);
|
||||
}
|
||||
}).start();
|
||||
});
|
||||
response = "{\"status\": \"success\", \"msg\": \"Suspending recording\"}";
|
||||
resp.getWriter().write(response);
|
||||
break;
|
||||
|
|
Loading…
Reference in New Issue