From 9b9659be71d1a03e84edba8a20c0f25d7a2e0518 Mon Sep 17 00:00:00 2001 From: 0xb00bface <0xboobface@gmail.com> Date: Sun, 5 Sep 2021 20:23:35 +0200 Subject: [PATCH] Improve recording relation action behaviour --- .../java/ctbrec/ui/RecordUntilDialog.java | 80 +++++++++++ .../ui/StreamSourceSelectionDialog.java | 136 ++++++++++++------ .../ctbrec/ui/action/AbstractModelAction.java | 49 ++++--- .../ctbrec/ui/action/SetStopDateAction.java | 49 +------ .../ui/action/StartRecordingAction.java | 60 +++++++- .../ctbrec/ui/menu/ModelMenuContributor.java | 93 +++++++----- .../main/java/ctbrec/ui/tabs/ThumbCell.java | 56 ++------ .../recorded/AbstractRecordedModelsTab.java | 29 ++-- 8 files changed, 350 insertions(+), 202 deletions(-) create mode 100644 client/src/main/java/ctbrec/ui/RecordUntilDialog.java diff --git a/client/src/main/java/ctbrec/ui/RecordUntilDialog.java b/client/src/main/java/ctbrec/ui/RecordUntilDialog.java new file mode 100644 index 00000000..a159823e --- /dev/null +++ b/client/src/main/java/ctbrec/ui/RecordUntilDialog.java @@ -0,0 +1,80 @@ +package ctbrec.ui; + +import static ctbrec.SubsequentAction.*; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ctbrec.Model; +import ctbrec.SubsequentAction; +import ctbrec.ui.controls.DateTimePicker; +import ctbrec.ui.controls.Dialogs; +import javafx.geometry.Insets; +import javafx.scene.Cursor; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.RadioButton; +import javafx.scene.control.ToggleGroup; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; + +public class RecordUntilDialog { + + private static final Logger LOG = LoggerFactory.getLogger(RecordUntilDialog.class); + + private Node source; + private Model model; + + private RadioButton pauseButton; + private DateTimePicker datePicker; + private GridPane gridPane = new GridPane(); + + public RecordUntilDialog(Node source, Model model) { + this.source = source; + this.model = model; + createGui(); + } + + private void createGui() { + source.setCursor(Cursor.WAIT); + datePicker = new DateTimePicker(); + gridPane.setHgap(10); + gridPane.setVgap(10); + gridPane.setPadding(new Insets(20, 150, 10, 10)); + gridPane.add(new Label("Stop at"), 0, 0); + gridPane.add(datePicker, 1, 0); + gridPane.add(new Label("And then"), 0, 1); + var toggleGroup = new ToggleGroup(); + pauseButton = new RadioButton("pause recording"); + pauseButton.setSelected(model.getRecordUntilSubsequentAction() == PAUSE); + pauseButton.setToggleGroup(toggleGroup); + var removeButton = new RadioButton("remove model"); + removeButton.setSelected(model.getRecordUntilSubsequentAction() == REMOVE); + removeButton.setToggleGroup(toggleGroup); + var row = new HBox(); + row.getChildren().addAll(pauseButton, removeButton); + HBox.setMargin(pauseButton, new Insets(5)); + HBox.setMargin(removeButton, new Insets(5)); + gridPane.add(row, 1, 1); + if (model.isRecordingTimeLimited()) { + var localDate = LocalDateTime.ofInstant(model.getRecordUntil(), ZoneId.systemDefault()); + datePicker.setDateTimeValue(localDate); + } + } + + public boolean showAndWait() { + boolean confirmed = Dialogs.showCustomInput(source.getScene(), "Stop Recording of " + model.getDisplayName() + " at", gridPane); + if (confirmed) { + SubsequentAction action = pauseButton.isSelected() ? PAUSE : REMOVE; + LOG.info("Stop at {} and {}", datePicker.getDateTimeValue(), action); + var stopAt = Instant.from(datePicker.getDateTimeValue().atZone(ZoneId.systemDefault())); + model.setRecordUntil(stopAt); + model.setRecordUntilSubsequentAction(action); + } + return confirmed; + } +} diff --git a/client/src/main/java/ctbrec/ui/StreamSourceSelectionDialog.java b/client/src/main/java/ctbrec/ui/StreamSourceSelectionDialog.java index 17fc2828..bb5b87d8 100644 --- a/client/src/main/java/ctbrec/ui/StreamSourceSelectionDialog.java +++ b/client/src/main/java/ctbrec/ui/StreamSourceSelectionDialog.java @@ -2,28 +2,51 @@ package ctbrec.ui; import java.util.Collections; import java.util.List; -import java.util.Optional; import java.util.concurrent.ExecutionException; -import java.util.function.Consumer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ctbrec.GlobalThreadPool; import ctbrec.Model; import ctbrec.recorder.download.StreamSource; import ctbrec.ui.controls.Dialogs; import javafx.concurrent.Task; +import javafx.concurrent.WorkerStateEvent; +import javafx.scene.Cursor; import javafx.scene.Scene; +import javafx.scene.control.ButtonType; import javafx.scene.control.ChoiceDialog; import javafx.scene.image.Image; import javafx.stage.Stage; -public class StreamSourceSelectionDialog { - private static final StreamSource BEST = new BestStreamSource(); +public class StreamSourceSelectionDialog extends ChoiceDialog { - private StreamSourceSelectionDialog() { - } + private static final Logger LOG = LoggerFactory.getLogger(StreamSourceSelectionDialog.class); - public static void show(Scene parent, Model model, Consumer onSuccess, Consumer onFail) { - Task> selectStreamSource = new Task>() { + public static final StreamSource BEST = new BestStreamSource(); + public static final StreamSource LOADING = new LoadingStreamSource(); + private Scene parent; + private Model model; + private Task> loadStreamSources; + + + public StreamSourceSelectionDialog(Scene parent, Model model) { + this.parent = parent; + this.model = model; + if (parent != null) { + getDialogPane().getScene().getStylesheets().addAll(parent.getStylesheets()); + } + changeCursorTo(Cursor.WAIT); + getItems().add(LOADING); + setSelectedItem(LOADING); + setTitle("Stream Quality"); + setHeaderText("Select your preferred stream quality for " + model.getDisplayName()); + var icon = Dialogs.class.getResourceAsStream("/icon.png"); + var stage = (Stage) getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image(icon)); + setResultConverter(bt -> (bt == ButtonType.OK) ? getSelectedItem() : null); + loadStreamSources = new Task>() { @Override protected List call() throws Exception { List sources = model.getStreamSources(); @@ -32,38 +55,56 @@ public class StreamSourceSelectionDialog { return sources; } }; - selectStreamSource.setOnSucceeded(e -> { - List sources; - try { - sources = selectStreamSource.get(); - var selectedIndex = model.getStreamUrlIndex() > -1 ? Math.min(model.getStreamUrlIndex(), sources.size() - 1) : sources.size() - 1; - var choiceDialog = new ChoiceDialog(sources.get(selectedIndex), sources); - choiceDialog.setTitle("Stream Quality"); - choiceDialog.setHeaderText("Select your preferred stream quality for " + model.getDisplayName()); - choiceDialog.setResizable(true); - var stage = (Stage) choiceDialog.getDialogPane().getScene().getWindow(); - stage.getScene().getStylesheets().addAll(parent.getStylesheets()); - var icon = Dialogs.class.getResourceAsStream("/icon.png"); - stage.getIcons().add(new Image(icon)); - Optional selectedSource = choiceDialog.showAndWait(); - if (selectedSource.isPresent()) { - int index = -1; - if (selectedSource.get() != BEST) { - index = sources.indexOf(selectedSource.get()); - } - model.setStreamUrlIndex(index); - System.err.println(model + " " + model.getStreamUrlIndex()); - onSuccess.accept(model); - } - } catch (ExecutionException e1) { - onFail.accept(e1); - } catch (InterruptedException e1) { - Thread.currentThread().interrupt(); - onFail.accept(e1); - } - }); - selectStreamSource.setOnFailed(e -> onFail.accept(selectStreamSource.getException())); - GlobalThreadPool.submit(selectStreamSource); + loadStreamSources.setOnFailed(this::onFailed); + loadStreamSources.setOnSucceeded(this::onSucceeded); + GlobalThreadPool.submit(loadStreamSources); + } + + private void onFailed(WorkerStateEvent evt) { + changeCursorTo(Cursor.DEFAULT); + try { + loadStreamSources.get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + LOG.error("Couldn't fetch available stream sources", e); + } catch (ExecutionException e) { + LOG.error("Couldn't fetch available stream sources", e); + } + + boolean confirmed = Dialogs.showConfirmDialog("Error loading stream resolutions", "Do you want to add the model anyway?", "Stream resolutions unknown", parent); + if (confirmed) { + getItems().clear(); + getItems().add(BEST); + setSelectedItem(BEST); + } else { + close(); + } + } + + private void onSucceeded(WorkerStateEvent evt) { + changeCursorTo(Cursor.DEFAULT); + List sources; + try { + sources = loadStreamSources.get(); + getItems().remove(LOADING); + getItems().addAll(sources); + var selectedIndex = model.getStreamUrlIndex() > -1 ? Math.min(model.getStreamUrlIndex(), sources.size() - 1) : sources.size() - 1; + setSelectedItem(getItems().get(selectedIndex)); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + showError(e); + } catch (ExecutionException e) { + showError(e); + } + } + + void changeCursorTo(Cursor cursor) { + parent.setCursor(cursor); + getDialogPane().setCursor(cursor); + } + + private void showError(Exception e) { + Dialogs.showError(parent, getHeaderText(), getContentText(), e); } private static class BestStreamSource extends StreamSource { @@ -72,4 +113,19 @@ public class StreamSourceSelectionDialog { return "Best"; } } + + private static class LoadingStreamSource extends StreamSource { + @Override + public String toString() { + return "Loading..."; + } + } + + public int indexOf(StreamSource selectedSource) { + int index = -1; + if (selectedSource != LOADING && selectedSource != BEST) { + index = getItems().indexOf(selectedSource); + } + return index; + } } diff --git a/client/src/main/java/ctbrec/ui/action/AbstractModelAction.java b/client/src/main/java/ctbrec/ui/action/AbstractModelAction.java index 3632d20a..1071a6bf 100644 --- a/client/src/main/java/ctbrec/ui/action/AbstractModelAction.java +++ b/client/src/main/java/ctbrec/ui/action/AbstractModelAction.java @@ -31,25 +31,36 @@ public abstract class AbstractModelAction { protected CompletableFuture> execute(String errorHeader, String errorMsg) { source.setCursor(Cursor.WAIT); - return CompletableFuture.supplyAsync(() -> { - final List result = new ArrayList<>(models.size()); - final List> futures = new ArrayList<>(models.size()); - for (Model model : models) { - futures.add(task - .executeSync(model) - .whenComplete((mdl, ex) -> - result.add(new Result(model, ex)))); - } - Platform.runLater(() -> source.setCursor(Cursor.DEFAULT)); - List failed = result.stream().filter(Result::failed).collect(Collectors.toList()); - if (!failed.isEmpty()) { - Throwable t = failed.get(0).getThrowable(); - String failedModelList = failed.stream().map(Result::getModel).map(Model::getDisplayName).collect(Collectors.joining(", ")); - String msg = MessageFormat.format(errorMsg, failedModelList); - Dialogs.showError(source.getScene(), errorHeader, msg, t); - } - return result; - }, GlobalThreadPool.get()); + return CompletableFuture.supplyAsync(() -> internalExecute(errorHeader, errorMsg), GlobalThreadPool.get()); + } + + protected CompletableFuture> executeSync(String errorHeader, String errorMsg) { + source.setCursor(Cursor.WAIT); + return CompletableFuture.completedFuture(internalExecute(errorHeader, errorMsg)); + } + + protected List internalExecute(String errorHeader, String errorMsg) { + final List result = new ArrayList<>(models.size()); + final List> futures = new ArrayList<>(models.size()); + for (Model model : models) { + futures.add(task + .executeSync(model) + .whenComplete((mdl, ex) -> + result.add(new Result(model, ex)))); + } + Platform.runLater(() -> source.setCursor(Cursor.DEFAULT)); + checkResultForErrors(errorHeader, errorMsg, result); + return result; + } + + protected void checkResultForErrors(String errorHeader, String errorMsg, List result) { + List failed = result.stream().filter(Result::failed).collect(Collectors.toList()); + if (!failed.isEmpty()) { + Throwable t = failed.get(0).getThrowable(); + String failedModelList = failed.stream().map(Result::getModel).map(Model::getDisplayName).collect(Collectors.joining(", ")); + String msg = MessageFormat.format(errorMsg, failedModelList); + Dialogs.showError(source.getScene(), errorHeader, msg, t); + } } public static class Result { diff --git a/client/src/main/java/ctbrec/ui/action/SetStopDateAction.java b/client/src/main/java/ctbrec/ui/action/SetStopDateAction.java index ee9cbf36..e373f204 100644 --- a/client/src/main/java/ctbrec/ui/action/SetStopDateAction.java +++ b/client/src/main/java/ctbrec/ui/action/SetStopDateAction.java @@ -1,13 +1,8 @@ package ctbrec.ui.action; -import static ctbrec.SubsequentAction.*; - import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; import java.util.concurrent.CompletableFuture; import org.slf4j.Logger; @@ -15,20 +10,13 @@ import org.slf4j.LoggerFactory; import ctbrec.GlobalThreadPool; import ctbrec.Model; -import ctbrec.SubsequentAction; import ctbrec.recorder.Recorder; -import ctbrec.ui.controls.DateTimePicker; +import ctbrec.ui.RecordUntilDialog; import ctbrec.ui.controls.Dialogs; import ctbrec.ui.tasks.StartRecordingTask; import javafx.application.Platform; -import javafx.geometry.Insets; import javafx.scene.Cursor; import javafx.scene.Node; -import javafx.scene.control.Label; -import javafx.scene.control.RadioButton; -import javafx.scene.control.ToggleGroup; -import javafx.scene.layout.GridPane; -import javafx.scene.layout.HBox; public class SetStopDateAction { @@ -38,9 +26,6 @@ public class SetStopDateAction { private Model model; private Recorder recorder; - private RadioButton pauseButton; - private DateTimePicker datePicker; - public SetStopDateAction(Node source, Model model, Recorder recorder) { this.source = source; this.model = model; @@ -49,31 +34,8 @@ public class SetStopDateAction { public CompletableFuture execute() { source.setCursor(Cursor.WAIT); - datePicker = new DateTimePicker(); - var gridPane = new GridPane(); - gridPane.setHgap(10); - gridPane.setVgap(10); - gridPane.setPadding(new Insets(20, 150, 10, 10)); - gridPane.add(new Label("Stop at"), 0, 0); - gridPane.add(datePicker, 1, 0); - gridPane.add(new Label("And then"), 0, 1); - var toggleGroup = new ToggleGroup(); - pauseButton = new RadioButton("pause recording"); - pauseButton.setSelected(model.getRecordUntilSubsequentAction() == PAUSE); - pauseButton.setToggleGroup(toggleGroup); - var removeButton = new RadioButton("remove model"); - removeButton.setSelected(model.getRecordUntilSubsequentAction() == REMOVE); - removeButton.setToggleGroup(toggleGroup); - var row = new HBox(); - row.getChildren().addAll(pauseButton, removeButton); - HBox.setMargin(pauseButton, new Insets(5)); - HBox.setMargin(removeButton, new Insets(5)); - gridPane.add(row, 1, 1); - if (model.isRecordingTimeLimited()) { - var localDate = LocalDateTime.ofInstant(model.getRecordUntil(), ZoneId.systemDefault()); - datePicker.setDateTimeValue(localDate); - } - boolean userClickedOk = Dialogs.showCustomInput(source.getScene(), "Stop Recording of " + model.getDisplayName() + " at", gridPane); + RecordUntilDialog dialog = new RecordUntilDialog(source, model); + boolean userClickedOk = dialog.showAndWait(); return createAsyncTask(userClickedOk); } @@ -92,11 +54,6 @@ public class SetStopDateAction { } private void setRecordingTimeLimit() { - SubsequentAction action = pauseButton.isSelected() ? PAUSE : REMOVE; - LOG.info("Stop at {} and {}", datePicker.getDateTimeValue(), action); - var stopAt = Instant.from(datePicker.getDateTimeValue().atZone(ZoneId.systemDefault())); - model.setRecordUntil(stopAt); - model.setRecordUntilSubsequentAction(action); try { if (!recorder.isTracked(model) || model.isMarkedForLaterRecording()) { new StartRecordingTask(recorder).executeSync(model) diff --git a/client/src/main/java/ctbrec/ui/action/StartRecordingAction.java b/client/src/main/java/ctbrec/ui/action/StartRecordingAction.java index 697bd439..6adc772f 100644 --- a/client/src/main/java/ctbrec/ui/action/StartRecordingAction.java +++ b/client/src/main/java/ctbrec/ui/action/StartRecordingAction.java @@ -1,20 +1,78 @@ package ctbrec.ui.action; +import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.concurrent.CompletableFuture; +import ctbrec.Config; import ctbrec.Model; import ctbrec.recorder.Recorder; +import ctbrec.recorder.download.StreamSource; +import ctbrec.ui.RecordUntilDialog; +import ctbrec.ui.StreamSourceSelectionDialog; import ctbrec.ui.tasks.StartRecordingTask; +import javafx.application.Platform; +import javafx.scene.Cursor; import javafx.scene.Node; public class StartRecordingAction extends AbstractModelAction { + private static final String ERROR_HEADER = "Couldn't start recording"; + private static final String ERROR_MSG = "Starting recording of {0} failed:"; + + private boolean showRecordUntilDialog = false; + public StartRecordingAction(Node source, List models, Recorder recorder) { super(source, models, recorder, new StartRecordingTask(recorder)); } + public StartRecordingAction showRecordUntilDialog() { + showRecordUntilDialog = true; + return this; + } + public CompletableFuture> execute() { - return super.execute("Couldn't start recording", "Starting recording of {0} failed:"); + boolean selectSource = Config.getInstance().getSettings().chooseStreamQuality; + if (selectSource || showRecordUntilDialog) { + var future = executeSync(); + Platform.runLater(() -> source.setCursor(Cursor.DEFAULT)); + return future; + } else { + return super.execute(ERROR_HEADER, ERROR_MSG); + } + } + + private CompletableFuture> executeSync() { + var result = new ArrayList(models.size()); + for (final Model model : models) { + if (showRecordUntilDialog) { + RecordUntilDialog dialog = new RecordUntilDialog(source, model); + var confirmed = dialog.showAndWait(); + if (!confirmed) { + continue; + } + } + + boolean selectSource = Config.getInstance().getSettings().chooseStreamQuality; + if (selectSource) { + var dialog = new StreamSourceSelectionDialog(source.getScene(), model); + Optional selection = dialog.showAndWait(); + if (selection.isPresent()) { + StreamSource src = selection.get(); + if (src != StreamSourceSelectionDialog.LOADING) { + int index = dialog.indexOf(src); + model.setStreamUrlIndex(index); + } + } else { + continue; + } + } + + new StartRecordingTask(recorder).executeSync(model) + .whenComplete((mdl, ex) -> result.add(new Result(mdl, ex))); + } + checkResultForErrors(ERROR_HEADER, ERROR_MSG, result); + return CompletableFuture.completedFuture(result); } } diff --git a/client/src/main/java/ctbrec/ui/menu/ModelMenuContributor.java b/client/src/main/java/ctbrec/ui/menu/ModelMenuContributor.java index 328df984..580f2916 100644 --- a/client/src/main/java/ctbrec/ui/menu/ModelMenuContributor.java +++ b/client/src/main/java/ctbrec/ui/menu/ModelMenuContributor.java @@ -15,6 +15,7 @@ import ctbrec.Config; import ctbrec.Model; import ctbrec.ModelGroup; import ctbrec.recorder.Recorder; +import ctbrec.recorder.download.StreamSource; import ctbrec.ui.AutosizeAlert; import ctbrec.ui.DesktopIntegration; import ctbrec.ui.StreamSourceSelectionDialog; @@ -34,6 +35,8 @@ import ctbrec.ui.action.TipAction; import ctbrec.ui.action.TriConsumer; import ctbrec.ui.controls.Dialogs; import ctbrec.ui.tabs.FollowedTab; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; import javafx.scene.Node; import javafx.scene.control.Alert; import javafx.scene.control.ContextMenu; @@ -47,9 +50,6 @@ public class ModelMenuContributor { private static final Logger LOG = LoggerFactory.getLogger(ModelMenuContributor.class); - private static final String COULDNT_START_STOP_RECORDING = "Couldn't start/stop recording"; - private static final String ERROR = "Error"; - private Config config; private Recorder recorder; private Node source; @@ -242,8 +242,8 @@ public class ModelMenuContributor { var couldntSwitchHeaderText = "Couldn't switch stream resolution"; try { - if (!selectedModel.isOnline(true)) { - Dialogs.showError(source.getScene(), couldntSwitchHeaderText, "The resolution can only be changed, when the model is online", null); + if (!selectedModel.isOnline()) { + Dialogs.showError(source.getScene(), couldntSwitchHeaderText, "The resolution can only be changed when the model is online", null); return; } } catch (InterruptedException e1) { @@ -255,19 +255,21 @@ public class ModelMenuContributor { return; } - Consumer onSuccess = m -> { - try { - recorder.switchStreamSource(m); - } catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) { - LOG.error(couldntSwitchHeaderText, e); - showStreamSwitchErrorDialog(e); + StreamSourceSelectionDialog dialog = new StreamSourceSelectionDialog(source.getScene(), selectedModel); + Optional selectedSource = dialog.showAndWait(); + if (selectedSource.isPresent()) { + StreamSource src = selectedSource.get(); + if (src != StreamSourceSelectionDialog.LOADING) { + int index = dialog.indexOf(selectedSource.get()); + selectedModel.setStreamUrlIndex(index); + try { + recorder.switchStreamSource(selectedModel); + } catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) { + LOG.error(couldntSwitchHeaderText, e); + showStreamSwitchErrorDialog(e); + } } - }; - Consumer onFail = t -> { - LOG.error(couldntSwitchHeaderText, t); - showStreamSwitchErrorDialog(t); - }; - StreamSourceSelectionDialog.show(source.getScene(), selectedModel, onSuccess, onFail); + } } private void showStreamSwitchErrorDialog(Throwable throwable) { @@ -343,13 +345,28 @@ public class ModelMenuContributor { private void addStartRecordingWithTimeLimit(ContextMenu menu, List selectedModels) { var model = selectedModels.get(0); - var text = recorder.isTracked(model) ? "Record Until" : "Start Recording Until"; + String text; + EventHandler eventHandler; + if (recorder.isTracked(model)) { + text = "Record Until"; + eventHandler = e -> { + for (Model selectedModel : selectedModels) { + new SetStopDateAction(source, selectedModel, recorder) + .execute() + .thenAccept(r -> executeCallback()); + } + }; + } else { + text = "Start Recording Until"; + eventHandler = e -> new StartRecordingAction(source, selectedModels, recorder) + .showRecordUntilDialog() + .execute() + .thenAccept(r -> executeCallback()); + } + var start = new MenuItem(text); + start.setOnAction(eventHandler); menu.getItems().add(start); - start.setOnAction(e -> { - selectedModels.forEach(m -> new SetStopDateAction(source, m, recorder).execute() // - .thenAccept(b -> executeCallback())); - }); } private void addRemoveTimeLimit(ContextMenu menu, List selectedModels) { @@ -395,22 +412,22 @@ public class ModelMenuContributor { private void startStopAction(List selection, boolean start) { if (start) { - boolean selectSource = Config.getInstance().getSettings().chooseStreamQuality; - if (selectSource) { - for (Model model : selection) { - Consumer onSuccess = modl -> startRecording(List.of(modl)); - Consumer onFail = throwable -> { - Alert alert = new AutosizeAlert(Alert.AlertType.ERROR, source.getScene()); - alert.setTitle(ERROR); - alert.setHeaderText(COULDNT_START_STOP_RECORDING); - alert.setContentText("I/O error while starting/stopping the recording: " + throwable.getLocalizedMessage()); - alert.showAndWait(); - }; - StreamSourceSelectionDialog.show(source.getScene(), model, onSuccess, onFail); - } - } else { - startRecording(selection); - } + // boolean selectSource = Config.getInstance().getSettings().chooseStreamQuality; + // if (selectSource) { + // for (Model model : selection) { + // Consumer onSuccess = modl -> startRecording(List.of(modl)); + // Consumer onFail = throwable -> { + // Alert alert = new AutosizeAlert(Alert.AlertType.ERROR, source.getScene()); + // alert.setTitle(ERROR); + // alert.setHeaderText(COULDNT_START_STOP_RECORDING); + // alert.setContentText("I/O error while starting/stopping the recording: " + throwable.getLocalizedMessage()); + // alert.showAndWait(); + // }; + // StreamSourceSelectionDialog.show(source.getScene(), model, onSuccess, onFail); + // } + // } else { + // } + startRecording(selection); } else { stopRecording(selection); } diff --git a/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java b/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java index 5ff46b11..e2c02ca2 100644 --- a/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java +++ b/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java @@ -5,12 +5,12 @@ import static ctbrec.io.HttpConstants.*; import static ctbrec.ui.Icon.*; import java.io.IOException; +import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; import java.util.stream.Collectors; import org.slf4j.Logger; @@ -29,9 +29,10 @@ import ctbrec.ui.AutosizeAlert; import ctbrec.ui.CamrecApplication; import ctbrec.ui.Icon; import ctbrec.ui.SiteUiFactory; -import ctbrec.ui.StreamSourceSelectionDialog; import ctbrec.ui.action.EditGroupAction; import ctbrec.ui.action.PlayAction; +import ctbrec.ui.action.StartRecordingAction; +import ctbrec.ui.action.StopRecordingAction; import ctbrec.ui.controls.Dialogs; import ctbrec.ui.controls.RecordingIndicator; import ctbrec.ui.controls.StreamPreview; @@ -70,7 +71,6 @@ import okhttp3.Response; public class ThumbCell extends StackPane { - private static final String COULDNT_START_STOP_RECORDING = "Couldn't start/stop recording"; private static final String ERROR = "Error"; private static final Logger LOG = LoggerFactory.getLogger(ThumbCell.class); private static final Duration ANIMATION_DURATION = new Duration(250); @@ -261,12 +261,18 @@ public class ThumbCell extends StackPane { pauseResumeAction(false); break; case BOOKMARKED: - recordLater(false); + forgetModel(); break; default: } } + private void forgetModel() { + new StopRecordingAction(this, List.of(model), recorder) + .execute() + .thenAccept(r -> update()); + } + private Node createPreviewTrigger() { var s = 24; previewTrigger = new StackPane(); @@ -514,21 +520,10 @@ public class ThumbCell extends StackPane { } void startStopAction(boolean start) { - setCursor(Cursor.WAIT); - - boolean selectSource = Config.getInstance().getSettings().chooseStreamQuality; - if (selectSource && start) { - Consumer onSuccess = modl -> startStopActionAsync(modl, true); - Consumer onFail = throwable -> { - Alert alert = new AutosizeAlert(Alert.AlertType.ERROR, getScene()); - alert.setTitle(ERROR); - alert.setHeaderText(COULDNT_START_STOP_RECORDING); - alert.setContentText("I/O error while starting/stopping the recording: " + throwable.getLocalizedMessage()); - alert.showAndWait(); - }; - StreamSourceSelectionDialog.show(getScene(), model, onSuccess, onFail); + if (start) { + new StartRecordingAction(this, List.of(getModel()), recorder).execute(); } else { - startStopActionAsync(model, start); + new StopRecordingAction(this, List.of(getModel()), recorder).execute(); } } @@ -557,26 +552,6 @@ public class ThumbCell extends StackPane { }); } - private void startStopActionAsync(Model model, boolean start) { - GlobalThreadPool.submit(() -> { - try { - if (start) { - recorder.addModel(model); - setRecording(!model.isMarkedForLaterRecording()); - } else { - recorder.stopRecording(model); - setRecording(false); - } - update(); - } catch (Exception e1) { - LOG.error(COULDNT_START_STOP_RECORDING, e1); - Dialogs.showError(getScene(), COULDNT_START_STOP_RECORDING, "I/O error while starting/stopping the recording: ", e1); - } finally { - Platform.runLater(() -> setCursor(Cursor.DEFAULT)); - } - }); - } - CompletableFuture follow(boolean follow) { setCursor(Cursor.WAIT); return CompletableFuture.supplyAsync(() -> { @@ -612,11 +587,6 @@ public class ThumbCell extends StackPane { }, GlobalThreadPool.get()); } - void recordLater(boolean recordLater) { - model.setMarkedForLaterRecording(recordLater); - startStopAction(recordLater); - } - public Model getModel() { return model; } diff --git a/client/src/main/java/ctbrec/ui/tabs/recorded/AbstractRecordedModelsTab.java b/client/src/main/java/ctbrec/ui/tabs/recorded/AbstractRecordedModelsTab.java index 28f5b64b..59199795 100644 --- a/client/src/main/java/ctbrec/ui/tabs/recorded/AbstractRecordedModelsTab.java +++ b/client/src/main/java/ctbrec/ui/tabs/recorded/AbstractRecordedModelsTab.java @@ -3,9 +3,6 @@ package ctbrec.ui.tabs.recorded; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.IOException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -32,8 +29,10 @@ import ctbrec.ui.AutosizeAlert; import ctbrec.ui.JavaFxModel; import ctbrec.ui.PreviewPopupHandler; import ctbrec.ui.action.AbstractPortraitAction.PortraitChangedEvent; +import ctbrec.ui.action.MarkForLaterRecordingAction; import ctbrec.ui.action.PlayAction; import ctbrec.ui.action.SetPortraitAction; +import ctbrec.ui.action.StartRecordingAction; import ctbrec.ui.controls.CustomMouseBehaviorContextMenu; import ctbrec.ui.controls.Dialogs; import ctbrec.ui.controls.SearchBox; @@ -109,9 +108,11 @@ public abstract class AbstractRecordedModelsTab extends Tab implements TabSelect protected ScrollPane scrollPane = new ScrollPane(); protected ContextMenu popup; + protected Config config; + AbstractRecordedModelsTab(String text) { super(text); - + config = Config.getInstance(); registerPortraitListener(); } @@ -347,11 +348,10 @@ public abstract class AbstractRecordedModelsTab extends Tab implements TabSelect for (Site site : sites) { var newModel = site.createModelFromUrl(url); if (newModel != null) { - try { - newModel.setMarkedForLaterRecording(getMarkModelsForLaterRecording()); - recorder.addModel(newModel); - } catch (IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) { - Dialogs.showError(getTabPane().getScene(), "Couldn't add model", "The model " + newModel.getName() + " could not be added: ", e1); + if (getMarkModelsForLaterRecording()) { + new MarkForLaterRecordingAction(getTabPane(), List.of(newModel), true, recorder).execute(); + } else { + new StartRecordingAction(getTabPane(), List.of(newModel), recorder).execute(); } return; } @@ -374,12 +374,11 @@ public abstract class AbstractRecordedModelsTab extends Tab implements TabSelect String modelName = parts[1]; for (Site site : sites) { if (Objects.equals(siteName.toLowerCase(), site.getClass().getSimpleName().toLowerCase())) { - try { - var m = site.createModel(modelName); - m.setMarkedForLaterRecording(getMarkModelsForLaterRecording()); - recorder.addModel(m); - } catch (IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) { - Dialogs.showError(getTabPane().getScene(), "Couldn't add model", "The model " + modelName + " could not be added:", e1); + var newModel = site.createModel(modelName); + if (getMarkModelsForLaterRecording()) { + new MarkForLaterRecordingAction(getTabPane(), List.of(newModel), true, recorder).execute(); + } else { + new StartRecordingAction(getTabPane(), List.of(newModel), recorder).execute(); } return; }