Add model group implementation for client server setup

This commit is contained in:
0xb00bface 2021-05-13 13:19:32 +02:00
parent c49a7db192
commit ba21dd2aeb
13 changed files with 217 additions and 67 deletions

View File

@ -1,6 +1,8 @@
package ctbrec.ui.action;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@ -49,7 +51,7 @@ public class AddToGroupAction {
if (StringUtil.isBlank(text)) {
return;
}
Set<ModelGroup> modelGroups = Config.getInstance().getSettings().modelGroups;
Set<ModelGroup> modelGroups = recorder.getModelGroups();
Optional<ModelGroup> existingGroup = modelGroups.stream().filter(mg -> mg.getName().equalsIgnoreCase(text)).findFirst();
if (existingGroup.isPresent()) {
existingGroup.get().add(model);
@ -63,7 +65,7 @@ public class AddToGroupAction {
recorder.saveModelGroup(group);
}
}
} catch (IOException e) {
} catch (IOException | InvalidKeyException | NoSuchAlgorithmException e) {
Dialogs.showError(source.getScene(), "Add model to group", "Saving model group failed", e);
} finally {
source.setCursor(Cursor.DEFAULT);

View File

@ -1,10 +1,12 @@
package ctbrec.ui.action;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.ModelGroup;
import ctbrec.recorder.Recorder;
@ -47,6 +49,12 @@ public class EditGroupAction {
group.getModelUrls().clear();
group.getModelUrls().addAll(dialog.getUrls());
recorder.saveModelGroup(group);
if (dialog.getUrls().isEmpty()) {
boolean delete = Dialogs.showConfirmDialog(DIALOG_TITLE, "Do you want to delete the group?", "Group is empty", source.getScene());
if (delete) {
recorder.deleteModelGroup(group);
}
}
}
} catch (Exception e) {
Dialogs.showError(source.getScene(), DIALOG_TITLE, "Editing model group failed", e);
@ -55,7 +63,7 @@ public class EditGroupAction {
}
}
private static class EditModelGroupDialog extends GridPane {
private class EditModelGroupDialog extends GridPane {
private TextField groupName;
private ListView<String> urlListView;
private ObservableList<String> urlList;
@ -63,13 +71,18 @@ public class EditGroupAction {
private List<String> urls;
public EditModelGroupDialog(Model model) {
Optional<ModelGroup> optionalModelGroup = Config.getInstance().getModelGroup(model);
if (optionalModelGroup.isPresent()) {
modelGroup = optionalModelGroup.get();
urls = new ArrayList<>(modelGroup.getModelUrls());
createGui(modelGroup);
} else {
Dialogs.showError(getScene(), DIALOG_TITLE, "No group found for model", null);
Optional<ModelGroup> optionalModelGroup;
try {
optionalModelGroup = recorder.getModelGroup(model);
if (optionalModelGroup.isPresent()) {
modelGroup = optionalModelGroup.get();
urls = new ArrayList<>(modelGroup.getModelUrls());
createGui(modelGroup);
} else {
Dialogs.showError(getScene(), DIALOG_TITLE, "No group found for model", null);
}
} catch (InvalidKeyException | NoSuchAlgorithmException | IOException e) {
Dialogs.showError(getScene(), DIALOG_TITLE, "Couldn't edit model group", e);
}
}

View File

@ -4,6 +4,8 @@ import static ctbrec.ui.controls.Dialogs.*;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
@ -46,6 +48,7 @@ import ctbrec.ui.action.IgnoreModelsAction;
import ctbrec.ui.action.OpenRecordingsDir;
import ctbrec.ui.action.SetStopDateAction;
import ctbrec.ui.controls.CustomMouseBehaviorContextMenu;
import ctbrec.ui.controls.Dialogs;
import ctbrec.ui.controls.FasterVerticalScrollPaneSkin;
import ctbrec.ui.controls.SearchBox;
import ctbrec.ui.controls.SearchPopover;
@ -543,7 +546,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
if (site.supportsTips()) {
contextMenu.getItems().add(sendTip);
}
Optional<ModelGroup> modelGroup = Config.getInstance().getModelGroup(model);
Optional<ModelGroup> modelGroup = getModelGroup(model);
contextMenu.getItems().add(modelGroup.isEmpty() ? addToGroup : editGroup);
contextMenu.getItems().addAll(copyUrl, openInBrowser, ignore, refresh, openRecDir);
if (model instanceof MyFreeCamsModel && Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
@ -555,6 +558,15 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
return contextMenu;
}
private Optional<ModelGroup> getModelGroup(Model model) {
try {
return recorder.getModelGroup(model);
} catch (InvalidKeyException | NoSuchAlgorithmException | IOException e) {
Dialogs.showError(grid.getScene(), "Error", "Couldn't get model group for model " + model, e);
return Optional.empty();
}
}
private void editGroup(Model model) {
new EditGroupAction(this.getContent(), recorder, model).execute();
}

View File

@ -1,5 +1,13 @@
package ctbrec.ui.tabs;
import static ctbrec.ui.controls.Dialogs.*;
import java.io.IOException;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Model;
import ctbrec.sites.Site;
import ctbrec.ui.SiteUiFactory;
@ -7,13 +15,6 @@ import ctbrec.ui.controls.SearchPopover;
import ctbrec.ui.controls.SearchPopoverTreeList;
import javafx.application.Platform;
import javafx.concurrent.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
import static ctbrec.ui.controls.Dialogs.showError;
public class ThumbOverviewTabSearchTask extends Task<List<Model>> {

View File

@ -2,19 +2,26 @@ package ctbrec.ui.tabs.recorded;
import static ctbrec.ui.Icon.*;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.ModelGroup;
import ctbrec.recorder.Recorder;
import ctbrec.ui.controls.Dialogs;
import javafx.scene.image.ImageView;
public class ModelNameTableCell extends IconTableCell<String> {
public ModelNameTableCell() {
private Recorder recorder;
public ModelNameTableCell(Recorder recorder) {
super(Map.of(GROUP_16, new ImageView(GROUP_16.url())));
this.recorder = recorder;
}
@Override
@ -26,7 +33,7 @@ public class ModelNameTableCell extends IconTableCell<String> {
if (item != null && !empty) {
setText(item);
Model m = getTableView().getItems().get(getTableRow().getIndex());
Optional<ModelGroup> optionalGroup = Config.getInstance().getModelGroup(m);
Optional<ModelGroup> optionalGroup = getModelGroup(m);
if (optionalGroup.isPresent()) {
ModelGroup group = optionalGroup.get();
setText(group.getName() + " (aka " + item + ')');
@ -37,4 +44,13 @@ public class ModelNameTableCell extends IconTableCell<String> {
}
super.updateItem(item, empty);
}
private Optional<ModelGroup> getModelGroup(Model model) {
try {
return recorder.getModelGroup(model);
} catch (InvalidKeyException | NoSuchAlgorithmException | IOException e) {
Dialogs.showError(getScene(), "Error", "Couldn't get model group for model " + model, e);
return Optional.empty();
}
}
}

View File

@ -173,7 +173,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
TableColumn<JavaFxModel, String> name = new TableColumn<>("Model");
name.setPrefWidth(200);
name.setCellValueFactory(new PropertyValueFactory<>("displayName"));
name.setCellFactory(param -> new ModelNameTableCell());
name.setCellFactory(param -> new ModelNameTableCell(recorder));
name.setEditable(false);
name.setId("name");
TableColumn<JavaFxModel, String> url = new TableColumn<>("URL");
@ -694,7 +694,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
} else {
menu.getItems().addAll(resumeRecording, pauseRecording);
}
Optional<ModelGroup> modelGroup = Config.getInstance().getModelGroup(selectedModels.get(0));
Optional<ModelGroup> modelGroup = getModelGroup(selectedModels.get(0));
menu.getItems().add(modelGroup.isEmpty() ? addToGroup : editGroup);
menu.getItems().addAll(copyUrl, openInPlayer, openInBrowser, openRecDir, switchStreamSource, follow, notes, ignore);
@ -709,6 +709,15 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
return menu;
}
private Optional<ModelGroup> getModelGroup(Model model) {
try {
return recorder.getModelGroup(model);
} catch (InvalidKeyException | NoSuchAlgorithmException | IOException e) {
Dialogs.showError(grid.getScene(), "Error", "Couldn't get model group for model " + model, e);
return Optional.empty();
}
}
private void addToGroup(Model model) {
new AddToGroupAction(this.getContent(), recorder, model).execute();
table.refresh();

View File

@ -311,10 +311,4 @@ public class Config {
public void enableSaving() {
savingDisabled = false;
}
public Optional<ModelGroup> getModelGroup(Model model) {
return getSettings().modelGroups.stream()
.filter(mg -> mg.getModelUrls().contains(model.getUrl()))
.findFirst();
}
}

View File

@ -783,16 +783,6 @@ public class NextGenLocalRecorder implements Recorder {
return config.getSettings().modelGroups;
}
@Override
public Optional<ModelGroup> getModelGroup(UUID id) {
for (ModelGroup group : getModelGroups()) {
if (Objects.equals(group.getId(), id)) {
return Optional.of(group);
}
}
return Optional.empty();
}
@Override
public void saveModelGroup(ModelGroup group) throws IOException {
Set<ModelGroup> modelGroups = config.getSettings().modelGroups;

View File

@ -6,7 +6,6 @@ import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import ctbrec.Model;
@ -153,17 +152,23 @@ public interface Recorder {
*/
public int getModelCount();
public Set<ModelGroup> getModelGroups();
public Set<ModelGroup> getModelGroups() throws InvalidKeyException, NoSuchAlgorithmException, IOException;
/**
* Saves a model group. If the group already exists, it will be overwritten. Otherwise it will
* be saved as a new group.
* @param group
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
*/
public void saveModelGroup(ModelGroup group) throws IOException;
public void saveModelGroup(ModelGroup group) throws IOException, InvalidKeyException, NoSuchAlgorithmException;
public Optional<ModelGroup> getModelGroup(UUID uuid);
public void deleteModelGroup(ModelGroup group) throws IOException, InvalidKeyException, NoSuchAlgorithmException;
public void deleteModelGroup(ModelGroup group) throws IOException;
default Optional<ModelGroup> getModelGroup(Model model) throws InvalidKeyException, NoSuchAlgorithmException, IOException {
return getModelGroups().stream()
.filter(mg -> mg.getModelUrls().contains(model.getUrl()))
.findFirst();
}
}

View File

@ -137,7 +137,7 @@ public class RecordingPreconditions {
}
private void ensureNoOtherFromModelGroupIsRecording(Model model) throws InvalidKeyException, NoSuchAlgorithmException, IOException {
Optional<ModelGroup> modelGroup = Config.getInstance().getModelGroup(model);
Optional<ModelGroup> modelGroup = recorder.getModelGroup(model);
if (modelGroup.isPresent()) {
for (String modelUrl : modelGroup.get().getModelUrls()) {
if (modelUrl.equals(model.getUrl())) {

View File

@ -9,9 +9,9 @@ import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
@ -37,6 +37,7 @@ import ctbrec.io.HttpClient;
import ctbrec.io.HttpException;
import ctbrec.io.InstantJsonAdapter;
import ctbrec.io.ModelJsonAdapter;
import ctbrec.io.UuidJSonAdapter;
import ctbrec.sites.Site;
import okhttp3.MediaType;
import okhttp3.Request;
@ -56,16 +57,20 @@ public class RemoteRecorder implements Recorder {
.add(Instant.class, new InstantJsonAdapter())
.add(Model.class, new ModelJsonAdapter())
.add(File.class, new FileJsonAdapter())
.add(UUID.class, new UuidJSonAdapter())
.build();
private JsonAdapter<ModelListResponse> modelListResponseAdapter = moshi.adapter(ModelListResponse.class);
private JsonAdapter<RecordingListResponse> recordingListResponseAdapter = moshi.adapter(RecordingListResponse.class);
private JsonAdapter<ModelRequest> modelRequestAdapter = moshi.adapter(ModelRequest.class);
private JsonAdapter<ModelGroupRequest> modelGroupRequestAdapter = moshi.adapter(ModelGroupRequest.class);
private JsonAdapter<ModelGroupListResponse> modelGroupListResponseAdapter = moshi.adapter(ModelGroupListResponse.class);
private JsonAdapter<RecordingRequest> recordingRequestAdapter = moshi.adapter(RecordingRequest.class);
private JsonAdapter<SimpleResponse> simpleResponseAdapter = moshi.adapter(SimpleResponse.class);
private List<Model> models = Collections.emptyList();
private List<Model> onlineModels = Collections.emptyList();
private List<Recording> recordings = Collections.emptyList();
private Set<ModelGroup> modelGroups = new HashSet<>();
private List<Site> sites;
private long spaceTotal = -1;
private long spaceFree = -1;
@ -106,7 +111,7 @@ public class RemoteRecorder implements Recorder {
private Optional<String> sendRequest(String action) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
String msg = "{\"action\": \"" + action + "\"}";
LOG.debug("Sending request to recording server: {}", msg);
LOG.trace("Sending request to recording server: {}", msg);
RequestBody requestBody = RequestBody.Companion.create(msg, JSON);
Request.Builder builder = new Request.Builder().url(getRecordingEndpoint()).post(requestBody);
addHmacIfNeeded(msg, builder);
@ -124,7 +129,7 @@ public class RemoteRecorder implements Recorder {
private void sendRequest(String action, Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
String payload = modelRequestAdapter.toJson(new ModelRequest(action, model));
LOG.debug("Sending request to recording server: {}", payload);
LOG.trace("Sending request to recording server: {}", payload);
RequestBody body = RequestBody.Companion.create(payload, JSON);
Request.Builder builder = new Request.Builder().url(getRecordingEndpoint()).post(body);
addHmacIfNeeded(payload, builder);
@ -154,6 +159,7 @@ public class RemoteRecorder implements Recorder {
String msg = recordingRequestAdapter.toJson(recReq);
RequestBody body = RequestBody.Companion.create(msg, JSON);
Request.Builder builder = new Request.Builder().url(getRecordingEndpoint()).post(body);
LOG.trace("Sending request to recording server: {}", msg);
addHmacIfNeeded(msg, builder);
Request request = builder.build();
try (Response response = client.execute(request)) {
@ -173,6 +179,33 @@ public class RemoteRecorder implements Recorder {
}
}
private void sendRequest(String action, ModelGroup model) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
String payload = modelGroupRequestAdapter.toJson(new ModelGroupRequest(action, model));
LOG.trace("Sending request to recording server: {}", payload);
RequestBody body = RequestBody.Companion.create(payload, JSON);
Request.Builder builder = new Request.Builder().url(getRecordingEndpoint()).post(body);
addHmacIfNeeded(payload, builder);
Request request = builder.build();
try (Response response = client.execute(request)) {
if (response.isSuccessful()) {
String json = response.body().string();
updateModelGroups(json);
} else {
throw new HttpException(response.code(), response.message());
}
}
}
private void updateModelGroups(String responseBody) throws IOException {
ModelGroupListResponse resp = modelGroupListResponseAdapter.fromJson(responseBody);
if (!resp.status.equals(SUCCESS)) {
throw new IOException("Server returned error " + resp.status + " " + resp.msg);
}
modelGroups.clear();
modelGroups.addAll(resp.groups);
}
private void addHmacIfNeeded(String msg, Builder builder) throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException {
if (Config.getInstance().getSettings().requireAuthentication) {
byte[] key = Config.getInstance().getSettings().key;
@ -240,10 +273,25 @@ public class RemoteRecorder implements Recorder {
syncOnlineModels();
syncSpace();
syncRecordings();
syncModelGroups();
sleep();
}
}
private void syncModelGroups() {
try {
sendRequest("listModelGroups").ifPresent(body -> {
try {
updateModelGroups(body);
} catch (IOException e) {
LOG.error("Error while loading model groups from server", e);
}
});
} catch (IOException | InvalidKeyException | NoSuchAlgorithmException e) {
LOG.error("Error while loading model groups from server", e);
}
}
private void syncSpace() {
try {
String msg = "{\"action\": \"space\"}";
@ -427,6 +475,12 @@ public class RemoteRecorder implements Recorder {
public List<Model> models;
}
private static class ModelGroupListResponse {
public String status;
public String msg;
public List<ModelGroup> groups;
}
private static class SimpleResponse {
public String status;
public String msg;
@ -475,6 +529,33 @@ public class RemoteRecorder implements Recorder {
}
}
public static class ModelGroupRequest {
private String action;
private ModelGroup modelGroup;
public ModelGroupRequest(String action, ModelGroup modelGroup) {
super();
this.action = action;
this.modelGroup = modelGroup;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public ModelGroup getModelGroup() {
return modelGroup;
}
public void setModelGroup(ModelGroup model) {
this.modelGroup = model;
}
}
public static class RecordingRequest {
private String action;
private Recording recording;
@ -609,28 +690,17 @@ public class RemoteRecorder implements Recorder {
}
@Override
public Set<ModelGroup> getModelGroups() {
// TODO Auto-generated method stub
return null;
public Set<ModelGroup> getModelGroups() throws InvalidKeyException, NoSuchAlgorithmException, IOException {
return modelGroups;
}
@Override
public void saveModelGroup(ModelGroup group) {
// TODO Auto-generated method stub
public void saveModelGroup(ModelGroup group) throws InvalidKeyException, NoSuchAlgorithmException, IOException {
sendRequest("saveModelGroup", group);
}
@Override
public void deleteModelGroup(ModelGroup group) {
// TODO Auto-generated method stub
}
@Override
public Optional<ModelGroup> getModelGroup(UUID id) {
for (ModelGroup group : getModelGroups()) {
if (Objects.equals(group.getId(), id)) {
return Optional.of(group);
}
}
return Optional.empty();
public void deleteModelGroup(ModelGroup group) throws InvalidKeyException, NoSuchAlgorithmException, IOException {
sendRequest("deleteModelGroup", group);
}
}

View File

@ -3,6 +3,9 @@ package ctbrec.recorder.postprocessing;
import static ctbrec.StringUtil.*;
import static java.util.Optional.*;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZoneOffset;
@ -15,6 +18,9 @@ import java.util.Optional;
import java.util.UUID;
import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.ModelGroup;
@ -24,10 +30,18 @@ import ctbrec.sites.Site;
public abstract class AbstractPlaceholderAwarePostProcessor extends AbstractPostProcessor {
private static final Logger LOG = LoggerFactory.getLogger(AbstractPlaceholderAwarePostProcessor.class);
public String fillInPlaceHolders(String input, PostProcessingContext ctx) {
Recording rec = ctx.getRecording();
Config config = ctx.getConfig();
Optional<ModelGroup> modelGroup = config.getModelGroup(rec.getModel());
Optional<ModelGroup> modelGroup;
try {
modelGroup = ctx.getRecorder().getModelGroup(rec.getModel());
} catch (InvalidKeyException | NoSuchAlgorithmException | IOException e) {
LOG.error("Couldn't get model group for {}", rec.getModel(), e);
return input;
}
Map<String, Function<String, Optional<String>>> placeholderValueSuppliers = new HashMap<>();
placeholderValueSuppliers.put("modelName", r -> ofNullable(rec.getModel().getName()));

View File

@ -10,6 +10,8 @@ import java.time.Instant;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@ -25,11 +27,13 @@ import com.squareup.moshi.Moshi;
import ctbrec.Config;
import ctbrec.GlobalThreadPool;
import ctbrec.Model;
import ctbrec.ModelGroup;
import ctbrec.Recording;
import ctbrec.io.BandwidthMeter;
import ctbrec.io.FileJsonAdapter;
import ctbrec.io.InstantJsonAdapter;
import ctbrec.io.ModelJsonAdapter;
import ctbrec.io.UuidJSonAdapter;
import ctbrec.recorder.Recorder;
import ctbrec.sites.Site;
@ -67,6 +71,7 @@ public class RecorderServlet extends AbstractCtbrecServlet {
.add(Instant.class, new InstantJsonAdapter())
.add(Model.class, new ModelJsonAdapter(sites))
.add(File.class, new FileJsonAdapter())
.add(UUID.class, new UuidJSonAdapter())
.build();
JsonAdapter<Request> requestAdapter = moshi.adapter(Request.class);
Request request = requestAdapter.fromJson(json);
@ -234,6 +239,17 @@ public class RecorderServlet extends AbstractCtbrecServlet {
response = "{\"status\": \"success\"}";
resp.getWriter().write(response);
break;
case "saveModelGroup":
recorder.saveModelGroup(request.modelGroup);
sendModelGroups(resp, recorder.getModelGroups());
break;
case "deleteModelGroup":
recorder.deleteModelGroup(request.modelGroup);
sendModelGroups(resp, recorder.getModelGroups());
break;
case "listModelGroups":
sendModelGroups(resp, recorder.getModelGroups());
break;
default:
resp.setStatus(SC_BAD_REQUEST);
response = "{\"status\": \"error\", \"msg\": \"Unknown action ["+request.action+"]\"}";
@ -258,6 +274,13 @@ public class RecorderServlet extends AbstractCtbrecServlet {
}
}
private void sendModelGroups(HttpServletResponse resp, Set<ModelGroup> modelGroups) throws IOException {
JSONObject jsonResponse = new JSONObject();
jsonResponse.put("status", "success");
jsonResponse.put("groups", modelGroups);
resp.getWriter().write(jsonResponse.toString());
}
private void startByUrl(Request request) throws InvalidKeyException, NoSuchAlgorithmException, IOException {
String url = request.model.getUrl();
for (Site site : sites) {
@ -291,5 +314,6 @@ public class RecorderServlet extends AbstractCtbrecServlet {
public String action;
public Model model;
public Recording recording;
public ModelGroup modelGroup;
}
}