forked from j62/ctbrec
1
0
Fork 0

Save model notes on server, when running in client/server mode

This commit is contained in:
0xb00bface 2023-04-25 19:16:37 +02:00
parent 63ffe78c36
commit 86e8f00aaf
23 changed files with 476 additions and 105 deletions

View File

@ -19,6 +19,9 @@ import ctbrec.io.BandwidthMeter;
import ctbrec.io.ByteUnitFormatter; import ctbrec.io.ByteUnitFormatter;
import ctbrec.io.HttpClient; import ctbrec.io.HttpClient;
import ctbrec.io.HttpException; import ctbrec.io.HttpException;
import ctbrec.notes.LocalModelNotesService;
import ctbrec.notes.ModelNotesService;
import ctbrec.notes.RemoteModelNotesService;
import ctbrec.recorder.NextGenLocalRecorder; import ctbrec.recorder.NextGenLocalRecorder;
import ctbrec.recorder.OnlineMonitor; import ctbrec.recorder.OnlineMonitor;
import ctbrec.recorder.Recorder; import ctbrec.recorder.Recorder;
@ -98,6 +101,7 @@ public class CamrecApplication extends Application {
private final List<Site> sites = new ArrayList<>(); private final List<Site> sites = new ArrayList<>();
public static HttpClient httpClient; public static HttpClient httpClient;
public static PortraitStore portraitStore; public static PortraitStore portraitStore;
public static ModelNotesService modelNotesService;
public static String title; public static String title;
private Stage primaryStage; private Stage primaryStage;
private RecordingsTab recordingsTab; private RecordingsTab recordingsTab;
@ -127,6 +131,7 @@ public class CamrecApplication extends Application {
initSites(); initSites();
startOnlineMonitor(); startOnlineMonitor();
createPortraitStore(); createPortraitStore();
createModelNotesService();
createGui(primaryStage); createGui(primaryStage);
checkForUpdates(); checkForUpdates();
registerClipboardListener(); registerClipboardListener();
@ -141,6 +146,14 @@ public class CamrecApplication extends Application {
} }
} }
private void createModelNotesService() {
if (config.getSettings().localRecording) {
modelNotesService = new LocalModelNotesService(config);
} else {
modelNotesService = new RemoteModelNotesService(httpClient, config);
}
}
private void registerTrayIconListener() { private void registerTrayIconListener() {
EventBusHolder.BUS.register(new Object() { EventBusHolder.BUS.register(new Object() {
@Subscribe @Subscribe
@ -244,7 +257,7 @@ public class CamrecApplication extends Application {
var modelsTab = new RecordedTab(recorder, sites); var modelsTab = new RecordedTab(recorder, sites);
tabPane.getTabs().add(modelsTab); tabPane.getTabs().add(modelsTab);
recordingsTab = new RecordingsTab("Recordings", recorder, config); recordingsTab = new RecordingsTab("Recordings", recorder, config, modelNotesService);
tabPane.getTabs().add(recordingsTab); tabPane.getTabs().add(recordingsTab);
if (config.getSettings().recentlyWatched) { if (config.getSettings().recentlyWatched) {
tabPane.getTabs().add(new RecentlyWatchedTab(recorder, sites)); tabPane.getTabs().add(new RecentlyWatchedTab(recorder, sites));

View File

@ -188,7 +188,7 @@ public class Player {
} }
private void expandPlaceHolders(String[] cmdline) { private void expandPlaceHolders(String[] cmdline) {
ModelVariableExpander expander = new ModelVariableExpander(model, Config.getInstance(), null, null); ModelVariableExpander expander = new ModelVariableExpander(model, CamrecApplication.modelNotesService, null, null);
for (int i = 0; i < cmdline.length; i++) { for (int i = 0; i < cmdline.length; i++) {
var param = cmdline[i]; var param = cmdline[i];
param = expander.expand(param); param = expander.expand(param);

View File

@ -1,23 +1,23 @@
package ctbrec.ui.action; package ctbrec.ui.action;
import ctbrec.Model;
import ctbrec.notes.ModelNotesService;
import ctbrec.ui.CamrecApplication;
import ctbrec.ui.controls.Dialogs;
import javafx.scene.Cursor;
import javafx.scene.Node;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.util.Optional; import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.ui.controls.Dialogs;
import javafx.scene.Cursor;
import javafx.scene.Node;
public class EditNotesAction { public class EditNotesAction {
private static final Logger LOG = LoggerFactory.getLogger(EditNotesAction.class); private static final Logger LOG = LoggerFactory.getLogger(EditNotesAction.class);
private Node source; private final Node source;
private Model model; private final Model model;
private Runnable callback; private final Runnable callback;
public EditNotesAction(Node source, Model selectedModel, Runnable callback) { public EditNotesAction(Node source, Model selectedModel, Runnable callback) {
this.source = source; this.source = source;
@ -27,26 +27,30 @@ public class EditNotesAction {
public void execute() { public void execute() {
source.setCursor(Cursor.WAIT); source.setCursor(Cursor.WAIT);
String notes = Config.getInstance().getSettings().modelNotes.getOrDefault(model.getUrl(), ""); ModelNotesService notesService = CamrecApplication.modelNotesService;
Optional<String> newNotes = Dialogs.showTextInput(source.getScene(), "Model Notes", "Notes for " + model.getName(), notes); try {
newNotes.ifPresent(n -> { String notes = notesService.loadModelNotes(model.getUrl()).orElse("");
if (!n.trim().isEmpty()) { Optional<String> newNotes = Dialogs.showTextInput(source.getScene(), "Model Notes", "Notes for " + model.getName(), notes);
Config.getInstance().getSettings().modelNotes.put(model.getUrl(), n); newNotes.ifPresent(n -> {
} else { try {
Config.getInstance().getSettings().modelNotes.remove(model.getUrl()); if (!n.trim().isEmpty()) {
} notesService.writeModelNotes(model.getUrl(), n);
try { } else {
Config.getInstance().save(); notesService.removeModelNotes(model.getUrl());
} catch (IOException e) { }
LOG.warn("Couldn't save config", e); } catch (IOException e) {
} LOG.warn("Couldn't save config", e);
}); }
if (callback != null) { });
try { if (callback != null) {
callback.run(); try {
} catch (Exception e) { callback.run();
LOG.error("Error while executing callback", e); } catch (Exception e) {
LOG.error("Error while executing callback", e);
}
} }
} catch (Exception e) {
Dialogs.showError(source.getScene(), "Model Notes", "Could not change model notes", e);
} }
source.setCursor(Cursor.DEFAULT); source.setCursor(Cursor.DEFAULT);
} }

View File

@ -6,6 +6,7 @@ import ctbrec.StringUtil;
import ctbrec.UnknownModel; import ctbrec.UnknownModel;
import ctbrec.recorder.Recorder; import ctbrec.recorder.Recorder;
import ctbrec.sites.chaturbate.Chaturbate; import ctbrec.sites.chaturbate.Chaturbate;
import ctbrec.ui.CamrecApplication;
import ctbrec.ui.controls.Dialogs; import ctbrec.ui.controls.Dialogs;
import ctbrec.variableexpansion.ConfigVariableExpander; import ctbrec.variableexpansion.ConfigVariableExpander;
import ctbrec.variableexpansion.ModelVariableExpander; import ctbrec.variableexpansion.ModelVariableExpander;
@ -77,7 +78,7 @@ public class VariablePlayGroundDialogFactory {
} }
}; };
ModelVariableExpander modelVariableExpander = new ModelVariableExpander(unknownModel, config, recorder, errorHandler); ModelVariableExpander modelVariableExpander = new ModelVariableExpander(unknownModel, CamrecApplication.modelNotesService, recorder, errorHandler);
RecordingVariableExpander recordingVariableExpander = new RecordingVariableExpander(recording, errorHandler); RecordingVariableExpander recordingVariableExpander = new RecordingVariableExpander(recording, errorHandler);
ConfigVariableExpander configVariableExpander = new ConfigVariableExpander(config, errorHandler); ConfigVariableExpander configVariableExpander = new ConfigVariableExpander(config, errorHandler);

View File

@ -5,6 +5,7 @@ import ctbrec.Recording.State;
import ctbrec.event.EventBusHolder; import ctbrec.event.EventBusHolder;
import ctbrec.event.RecordingStateChangedEvent; import ctbrec.event.RecordingStateChangedEvent;
import ctbrec.io.UrlUtil; import ctbrec.io.UrlUtil;
import ctbrec.notes.ModelNotesService;
import ctbrec.recorder.ProgressListener; import ctbrec.recorder.ProgressListener;
import ctbrec.recorder.Recorder; import ctbrec.recorder.Recorder;
import ctbrec.recorder.RecordingPinnedException; import ctbrec.recorder.RecordingPinnedException;
@ -74,6 +75,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener, Shutdown
private ScheduledService<List<JavaFxRecording>> updateService; private ScheduledService<List<JavaFxRecording>> updateService;
private final Config config; private final Config config;
private final ModelNotesService modelNotesService;
private final Recorder recorder; private final Recorder recorder;
private long spaceTotal = -1; private long spaceTotal = -1;
private long spaceFree = -1; private long spaceFree = -1;
@ -89,10 +91,11 @@ public class RecordingsTab extends Tab implements TabSelectionListener, Shutdown
Label spaceUsedValue; Label spaceUsedValue;
Lock recordingsLock = new ReentrantLock(); Lock recordingsLock = new ReentrantLock();
public RecordingsTab(String title, Recorder recorder, Config config) { public RecordingsTab(String title, Recorder recorder, Config config, ModelNotesService modelNotesService) {
super(title); super(title);
this.recorder = recorder; this.recorder = recorder;
this.config = config; this.config = config;
this.modelNotesService = modelNotesService;
createGui(); createGui();
setClosable(false); setClosable(false);
initializeUpdateService(); initializeUpdateService();
@ -156,7 +159,16 @@ public class RecordingsTab extends Tab implements TabSelectionListener, Shutdown
TableColumn<JavaFxRecording, String> modelNotes = new TableColumn<>("Model Notes"); TableColumn<JavaFxRecording, String> modelNotes = new TableColumn<>("Model Notes");
modelNotes.setId("modelNotes"); modelNotes.setId("modelNotes");
modelNotes.setPrefWidth(400); modelNotes.setPrefWidth(400);
modelNotes.setCellValueFactory(cdf -> new SimpleStringProperty(config.getModelNotes(cdf.getValue().getModel()))); modelNotes.setCellValueFactory(cdf -> {
String modelNts;
try {
modelNts = modelNotesService.loadModelNotes(cdf.getValue().getModel().getUrl()).orElse("");
} catch (IOException e) {
LOG.warn("Could not load model notes", e);
modelNts = "";
}
return new SimpleStringProperty(modelNts);
});
table.getColumns().addAll(name, date, status, progress, size, resolution, notes, modelNotes); table.getColumns().addAll(name, date, status, progress, size, resolution, notes, modelNotes);
table.setItems(observableRecordings); table.setItems(observableRecordings);
@ -261,7 +273,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener, Shutdown
Recording recording = table.getSelectionModel().getSelectedItem(); Recording recording = table.getSelectionModel().getSelectedItem();
if (recording != null) { if (recording != null) {
var state = recording.getStatus(); var state = recording.getStatus();
if(state == FINISHED || state == RECORDING) { if (state == FINISHED || state == RECORDING) {
play(recording); play(recording);
} }
} }
@ -448,10 +460,10 @@ public class RecordingsTab extends Tab implements TabSelectionListener, Shutdown
var tmp = new CustomMouseBehaviorContextMenu(); var tmp = new CustomMouseBehaviorContextMenu();
ModelMenuContributor.newContributor(getTabPane(), Config.getInstance(), recorder) // ModelMenuContributor.newContributor(getTabPane(), Config.getInstance(), recorder) //
.withStartStopCallback(m -> getTabPane().setCursor(Cursor.DEFAULT)) // .withStartStopCallback(m -> getTabPane().setCursor(Cursor.DEFAULT)) //
.removeModelAfterIgnore(true) // .removeModelAfterIgnore(true) //
.afterwards(table::refresh) // .afterwards(table::refresh) //
.contributeToMenu(List.of(recordings.get(0).getModel()), tmp); .contributeToMenu(List.of(recordings.get(0).getModel()), tmp);
var modelSubMenu = new Menu("Model"); var modelSubMenu = new Menu("Model");
modelSubMenu.getItems().addAll(tmp.getItems()); modelSubMenu.getItems().addAll(tmp.getItems());
contextMenu.getItems().add(modelSubMenu); contextMenu.getItems().add(modelSubMenu);

View File

@ -79,7 +79,7 @@ public abstract class AbstractRecordedModelsTab extends Tab implements TabSelect
protected StatePersistingTableView<JavaFxModel> table; protected StatePersistingTableView<JavaFxModel> table;
protected List<TableColumn<JavaFxModel, ?>> columns = new ArrayList<>(); protected List<TableColumn<JavaFxModel, ?>> columns = new ArrayList<>();
protected LoadingCache<Model, Image> portraitCache = CacheBuilder.newBuilder() protected LoadingCache<Model, Image> portraitCache = CacheBuilder.newBuilder()
.expireAfterAccess(1, TimeUnit.DAYS) .expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(1000) .maximumSize(1000)
.build(CacheLoader.from(this::loadModelPortrait)); .build(CacheLoader.from(this::loadModelPortrait));
@ -346,7 +346,12 @@ public abstract class AbstractRecordedModelsTab extends Tab implements TabSelect
@Override @Override
public String get() { public String get() {
String modelNotes = Config.getInstance().getModelNotes(m); String modelNotes;
try {
modelNotes = CamrecApplication.modelNotesService.loadModelNotes(m.getUrl()).orElse("");
} catch (IOException e) {
throw new RuntimeException(e);
}
return modelNotes; return modelNotes;
} }
}; };

View File

@ -345,9 +345,9 @@ public class Config {
} }
} }
public String getModelNotes(Model m) { // public String getModelNotes(Model m) {
return Config.getInstance().getSettings().modelNotes.getOrDefault(m.getUrl(), ""); // return Config.getInstance().getSettings().modelNotes.getOrDefault(m.getUrl(), "");
} // }
public void disableSaving() { public void disableSaving() {
savingDisabled = true; savingDisabled = true;

View File

@ -0,0 +1,23 @@
package ctbrec;
import okhttp3.Request;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import static java.nio.charset.StandardCharsets.UTF_8;
public class RemoteService {
protected void addHmacIfNeeded(byte[] msg, Request.Builder builder) throws InvalidKeyException, NoSuchAlgorithmException {
if (Config.getInstance().getSettings().requireAuthentication) {
byte[] key = Config.getInstance().getSettings().key;
String hmac = Hmac.calculate(msg, key);
builder.addHeader("CTBREC-HMAC", hmac);
}
}
protected void addHmacIfNeeded(String msg, Request.Builder builder) throws InvalidKeyException, NoSuchAlgorithmException {
addHmacIfNeeded(msg.getBytes(UTF_8), builder);
}
}

View File

@ -3,6 +3,7 @@ package ctbrec.image;
import ctbrec.Config; import ctbrec.Config;
import ctbrec.GlobalThreadPool; import ctbrec.GlobalThreadPool;
import ctbrec.Hmac; import ctbrec.Hmac;
import ctbrec.RemoteService;
import ctbrec.io.HttpClient; import ctbrec.io.HttpClient;
import ctbrec.io.HttpException; import ctbrec.io.HttpException;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -24,7 +25,7 @@ import static ctbrec.io.HttpConstants.MIMETYPE_IMAGE_JPG;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
@Slf4j @Slf4j
public class RemotePortraitStore implements PortraitStore { public class RemotePortraitStore extends RemoteService implements PortraitStore {
private final HttpClient httpClient; private final HttpClient httpClient;
private final Config config; private final Config config;
@ -99,14 +100,6 @@ public class RemotePortraitStore implements PortraitStore {
} }
} }
private void addHmacIfNeeded(byte[] msg, Request.Builder builder) throws InvalidKeyException, NoSuchAlgorithmException {
if (Config.getInstance().getSettings().requireAuthentication) {
byte[] key = Config.getInstance().getSettings().key;
String hmac = Hmac.calculate(msg, key);
builder.addHeader("CTBREC-HMAC", hmac);
}
}
@Override @Override
public void writePortrait(String modelUrl, byte[] data) throws IOException { public void writePortrait(String modelUrl, byte[] data) throws IOException {
RequestBody body = RequestBody.create(data, MediaType.parse(MIMETYPE_IMAGE_JPG)); RequestBody body = RequestBody.create(data, MediaType.parse(MIMETYPE_IMAGE_JPG));

View File

@ -0,0 +1,47 @@
package ctbrec.notes;
import ctbrec.Config;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
@Slf4j
@RequiredArgsConstructor
public class LocalModelNotesService implements ModelNotesService {
private final Config config;
@Override
public Optional<String> loadModelNotes(String modelUrl) {
return Optional.ofNullable(config.getSettings().modelNotes.get(modelUrl));
}
@Override
public Map<String, String> loadAllModelNotes() throws IOException {
return Collections.unmodifiableMap(config.getSettings().modelNotes);
}
@Override
public void writeModelNotes(String modelUrl, String notes) {
config.getSettings().modelNotes.put(modelUrl, notes);
save();
}
@Override
public void removeModelNotes(String modelUrl) {
config.getSettings().modelNotes.remove(modelUrl);
save();
}
private void save() {
try {
config.save();
} catch (IOException e) {
log.warn("Could not save the settings", e);
}
}
}

View File

@ -0,0 +1,16 @@
package ctbrec.notes;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
public interface ModelNotesService {
Optional<String> loadModelNotes(String modelUrl) throws IOException;
Map<String, String> loadAllModelNotes() throws IOException;
void writeModelNotes(String modelUrl, String notes) throws IOException;
void removeModelNotes(String modelUrl) throws IOException;
}

View File

@ -0,0 +1,154 @@
package ctbrec.notes;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import ctbrec.Config;
import ctbrec.GlobalThreadPool;
import ctbrec.RemoteService;
import ctbrec.io.HttpClient;
import ctbrec.io.HttpException;
import lombok.extern.slf4j.Slf4j;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.IOException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import static java.nio.charset.StandardCharsets.UTF_8;
@Slf4j
public class RemoteModelNotesService extends RemoteService implements ModelNotesService {
private final HttpClient httpClient;
private final Config config;
private final LoadingCache<String, String> cache = CacheBuilder.newBuilder()
.expireAfterWrite(3, TimeUnit.SECONDS)
.maximumSize(10000)
.build(CacheLoader.from(this::updateCache));
public RemoteModelNotesService(HttpClient httpClient, Config config) {
this.httpClient = httpClient;
this.config = config;
transferOldNotesToServer(config.getSettings().modelNotes);
}
private void transferOldNotesToServer(Map<String, String> modelNotes) {
LocalModelNotesService localModelNotesStore = new LocalModelNotesService(config);
GlobalThreadPool.submit(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
List<String> successfullyTransfered = new ArrayList<>();
for (Map.Entry<String, String> entry : modelNotes.entrySet()) {
try {
log.info("Uploading model notes to server {} - {}", entry.getKey(), entry.getValue());
RemoteModelNotesService.this.writeModelNotes(entry.getKey(), entry.getValue());
successfullyTransfered.add(entry.getKey());
} catch (Exception e) {
log.warn("Could not transfer model notes from local to remote store: {} {} - {}", entry.getKey(), entry.getValue(), e.getLocalizedMessage());
}
}
for (String s : successfullyTransfered) {
localModelNotesStore.removeModelNotes(s);
}
});
}
private String updateCache(String modelUrl) {
try {
var modelNotes = loadAllModelNotes();
for (Map.Entry<String, String> entry : modelNotes.entrySet()) {
cache.put(entry.getKey(), entry.getValue());
}
return modelNotes.get(modelUrl);
} catch (Exception e) {
throw new CacheLoader.InvalidCacheLoadException("Loading of model notes from server failed");
}
}
@Override
public Map<String, String> loadAllModelNotes() throws IOException {
Request.Builder builder = new Request.Builder().url(config.getServerUrl() + "/models/notes");
try {
addHmacIfNeeded(new byte[0], builder);
log.trace("Loading all model notes from server");
try (Response resp = httpClient.execute(builder.build())) {
if (resp.isSuccessful()) {
Map<String, String> result = new HashMap<>();
JSONObject json = new JSONObject(resp.body().string());
JSONArray names = json.names();
for (int i = 0; i < names.length(); i++) {
String name = names.getString(i);
result.put(name, json.getString(name));
}
return Collections.unmodifiableMap(result);
} else {
throw new HttpException(resp.code(), resp.message());
}
}
} catch (InvalidKeyException | NoSuchAlgorithmException e) {
throw new IOException("Could not load model notes from server", e);
}
}
@Override
public Optional<String> loadModelNotes(String modelUrl) throws IOException {
try {
log.trace("Loading model notes for {}", modelUrl);
return Optional.of(cache.get(modelUrl));
} catch (ExecutionException e) {
throw new IOException(e);
}
}
@Override
public void writeModelNotes(String modelUrl, String notes) throws IOException {
Request.Builder builder = new Request.Builder()
.url(config.getServerUrl() + "/models/notes/" + URLEncoder.encode(modelUrl, UTF_8))
.post(RequestBody.create(notes, MediaType.parse("text/plain")));
try {
addHmacIfNeeded(notes, builder);
try (Response resp = httpClient.execute(builder.build())) {
if (resp.isSuccessful()) {
cache.put(modelUrl, notes);
} else {
throw new HttpException(resp.code(), resp.message());
}
}
} catch (InvalidKeyException | NoSuchAlgorithmException e) {
throw new IOException("Could not write model notes to server", e);
}
}
@Override
public void removeModelNotes(String modelUrl) throws IOException {
Request.Builder builder = new Request.Builder()
.url(config.getServerUrl() + "/models/notes/" + URLEncoder.encode(modelUrl, UTF_8))
.delete();
try {
addHmacIfNeeded(new byte[0], builder);
try (Response resp = httpClient.execute(builder.build())) {
if (resp.isSuccessful()) {
cache.invalidate(modelUrl);
} else {
throw new HttpException(resp.code(), resp.message());
}
}
} catch (InvalidKeyException | NoSuchAlgorithmException e) {
throw new IOException("Could not delete model notes from server", e);
}
}
}

View File

@ -5,6 +5,7 @@ import ctbrec.*;
import ctbrec.Recording.State; import ctbrec.Recording.State;
import ctbrec.event.*; import ctbrec.event.*;
import ctbrec.io.HttpClient; import ctbrec.io.HttpClient;
import ctbrec.notes.LocalModelNotesService;
import ctbrec.recorder.download.Download; import ctbrec.recorder.download.Download;
import ctbrec.recorder.postprocessing.PostProcessingContext; import ctbrec.recorder.postprocessing.PostProcessingContext;
import ctbrec.recorder.postprocessing.PostProcessor; import ctbrec.recorder.postprocessing.PostProcessor;
@ -228,6 +229,7 @@ public class NextGenLocalRecorder implements Recorder {
ctx.setRecorder(this); ctx.setRecorder(this);
ctx.setRecording(recording); ctx.setRecording(recording);
ctx.setRecordingManager(recordingManager); ctx.setRecordingManager(recordingManager);
ctx.setModelNotesService(new LocalModelNotesService(config));
return ctx; return ctx;
} }

View File

@ -2,6 +2,7 @@ package ctbrec.recorder.postprocessing;
import ctbrec.Config; import ctbrec.Config;
import ctbrec.Recording; import ctbrec.Recording;
import ctbrec.notes.ModelNotesService;
import ctbrec.variableexpansion.ConfigVariableExpander; import ctbrec.variableexpansion.ConfigVariableExpander;
import ctbrec.variableexpansion.ModelVariableExpander; import ctbrec.variableexpansion.ModelVariableExpander;
import ctbrec.variableexpansion.RecordingVariableExpander; import ctbrec.variableexpansion.RecordingVariableExpander;
@ -18,8 +19,9 @@ public abstract class AbstractPlaceholderAwarePostProcessor extends AbstractPost
public String fillInPlaceHolders(String input, PostProcessingContext ctx, AntlrSyntacErrorAdapter errorListener) { public String fillInPlaceHolders(String input, PostProcessingContext ctx, AntlrSyntacErrorAdapter errorListener) {
Recording rec = ctx.getRecording(); Recording rec = ctx.getRecording();
Config config = ctx.getConfig(); Config config = ctx.getConfig();
ModelNotesService modelNotesService = ctx.getModelNotesService();
ModelVariableExpander modelExpander = new ModelVariableExpander(rec.getModel(), config, ctx.getRecorder(), errorListener); ModelVariableExpander modelExpander = new ModelVariableExpander(rec.getModel(), modelNotesService, ctx.getRecorder(), errorListener);
RecordingVariableExpander recordingExpander = new RecordingVariableExpander(rec, errorListener); RecordingVariableExpander recordingExpander = new RecordingVariableExpander(rec, errorListener);
ConfigVariableExpander configExpander = new ConfigVariableExpander(config, errorListener); ConfigVariableExpander configExpander = new ConfigVariableExpander(config, errorListener);
modelExpander.getPlaceholderValueSuppliers().putAll(recordingExpander.getPlaceholderValueSuppliers()); modelExpander.getPlaceholderValueSuppliers().putAll(recordingExpander.getPlaceholderValueSuppliers());

View File

@ -2,45 +2,17 @@ package ctbrec.recorder.postprocessing;
import ctbrec.Config; import ctbrec.Config;
import ctbrec.Recording; import ctbrec.Recording;
import ctbrec.notes.ModelNotesService;
import ctbrec.recorder.Recorder; import ctbrec.recorder.Recorder;
import ctbrec.recorder.RecordingManager; import ctbrec.recorder.RecordingManager;
import lombok.Data;
@Data
public class PostProcessingContext { public class PostProcessingContext {
private Recorder recorder; private Recorder recorder;
private Recording recording; private Recording recording;
private RecordingManager recordingManager; private RecordingManager recordingManager;
private Config config; private Config config;
private ModelNotesService modelNotesService;
public Recorder getRecorder() {
return recorder;
}
public void setRecorder(Recorder recorder) {
this.recorder = recorder;
}
public Recording getRecording() {
return recording;
}
public void setRecording(Recording recording) {
this.recording = recording;
}
public RecordingManager getRecordingManager() {
return recordingManager;
}
public void setRecordingManager(RecordingManager recordingManager) {
this.recordingManager = recordingManager;
}
public Config getConfig() {
return config;
}
public void setConfig(Config config) {
this.config = config;
}
} }

View File

@ -1,26 +1,28 @@
package ctbrec.variableexpansion; package ctbrec.variableexpansion;
import ctbrec.Config;
import ctbrec.Model; import ctbrec.Model;
import ctbrec.ModelGroup; import ctbrec.ModelGroup;
import ctbrec.StringUtil; import ctbrec.StringUtil;
import ctbrec.notes.ModelNotesService;
import ctbrec.recorder.Recorder; import ctbrec.recorder.Recorder;
import ctbrec.sites.Site; import ctbrec.sites.Site;
import ctbrec.variableexpansion.functions.AntlrSyntacErrorAdapter; import ctbrec.variableexpansion.functions.AntlrSyntacErrorAdapter;
import lombok.extern.slf4j.Slf4j;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import static java.util.Optional.ofNullable; import static java.util.Optional.ofNullable;
@Slf4j
public class ModelVariableExpander extends AbstractVariableExpander { public class ModelVariableExpander extends AbstractVariableExpander {
public ModelVariableExpander(Model model, Config config, Recorder recorder, AntlrSyntacErrorAdapter errorListener) { public ModelVariableExpander(Model model, ModelNotesService modelNotesService, Recorder recorder, AntlrSyntacErrorAdapter errorListener) {
super(errorListener); super(errorListener);
Optional<ModelGroup> modelGroup = Optional.ofNullable(recorder).flatMap(r -> r.getModelGroup(model)); Optional<ModelGroup> modelGroup = Optional.ofNullable(recorder).flatMap(r -> r.getModelGroup(model));
placeholderValueSuppliers.put("modelName", ofNullable(model.getName())); placeholderValueSuppliers.put("modelName", ofNullable(model.getName()));
placeholderValueSuppliers.put("modelDisplayName", ofNullable(model.getDisplayName())); placeholderValueSuppliers.put("modelDisplayName", ofNullable(model.getDisplayName()));
placeholderValueSuppliers.put("modelNotes", getSanitizedModelNotes(config, model)); placeholderValueSuppliers.put("modelNotes", getSanitizedModelNotes(modelNotesService, model));
placeholderValueSuppliers.put("siteName", ofNullable(model).map(Model::getSite).map(Site::getName)); placeholderValueSuppliers.put("siteName", ofNullable(model).map(Model::getSite).map(Site::getName));
placeholderValueSuppliers.put("modelGroupName", modelGroup.map(ModelGroup::getName)); placeholderValueSuppliers.put("modelGroupName", modelGroup.map(ModelGroup::getName));
placeholderValueSuppliers.put("modelGroupId", modelGroup.map(ModelGroup::getId).map(UUID::toString)); placeholderValueSuppliers.put("modelGroupId", modelGroup.map(ModelGroup::getId).map(UUID::toString));
@ -30,7 +32,12 @@ public class ModelVariableExpander extends AbstractVariableExpander {
return fillInPlaceHolders(input, placeholderValueSuppliers); return fillInPlaceHolders(input, placeholderValueSuppliers);
} }
private Optional<Object> getSanitizedModelNotes(Config config, Model m) { private Optional<Object> getSanitizedModelNotes(ModelNotesService modelNotesService, Model m) {
return ofNullable(config.getModelNotes(m)).map(StringUtil::sanitize); try {
return modelNotesService.loadModelNotes(m.getUrl()).map(StringUtil::sanitize);
} catch (Exception e) {
log.warn("Could not load model notes", e);
return Optional.empty();
}
} }
} }

View File

@ -2,6 +2,7 @@ package ctbrec.recorder.postprocessing;
import ctbrec.Config; import ctbrec.Config;
import ctbrec.Recording; import ctbrec.Recording;
import ctbrec.notes.ModelNotesService;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -14,6 +15,7 @@ import java.util.Locale;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
class AbstractPlaceholderAwarePostProcessorTest extends AbstractPpTest { class AbstractPlaceholderAwarePostProcessorTest extends AbstractPpTest {
@ -21,6 +23,7 @@ class AbstractPlaceholderAwarePostProcessorTest extends AbstractPpTest {
Recording rec; Recording rec;
Config config; Config config;
Move placeHolderAwarePp; Move placeHolderAwarePp;
ModelNotesService modelNotesService;
@Override @Override
@BeforeEach @BeforeEach
@ -34,6 +37,8 @@ class AbstractPlaceholderAwarePostProcessorTest extends AbstractPpTest {
rec.setSingleFile(true); rec.setSingleFile(true);
config = mockConfig(); config = mockConfig();
placeHolderAwarePp = new Move(); placeHolderAwarePp = new Move();
modelNotesService = mock(ModelNotesService.class);
when(modelNotesService.loadModelNotes(any())).thenReturn(null);
} }
@Test @Test
@ -157,7 +162,6 @@ class AbstractPlaceholderAwarePostProcessorTest extends AbstractPpTest {
@Test @Test
void testMissingValueForPlaceholder() { void testMissingValueForPlaceholder() {
String input = "asdf_${modelNotes}_asdf"; String input = "asdf_${modelNotes}_asdf";
when(config.getModelNotes(any())).thenReturn(null);
assertEquals("asdf_${modelNotes}_asdf", placeHolderAwarePp.fillInPlaceHolders(input, createPostProcessingContext(rec, null, config))); assertEquals("asdf_${modelNotes}_asdf", placeHolderAwarePp.fillInPlaceHolders(input, createPostProcessingContext(rec, null, config)));
} }

View File

@ -4,6 +4,7 @@ import ctbrec.Config;
import ctbrec.Model; import ctbrec.Model;
import ctbrec.Recording; import ctbrec.Recording;
import ctbrec.Settings; import ctbrec.Settings;
import ctbrec.notes.ModelNotesService;
import ctbrec.recorder.Recorder; import ctbrec.recorder.Recorder;
import ctbrec.recorder.RecordingManager; import ctbrec.recorder.RecordingManager;
import ctbrec.sites.Site; import ctbrec.sites.Site;
@ -18,6 +19,7 @@ import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Instant; import java.time.Instant;
import java.util.Optional;
import static java.nio.file.StandardOpenOption.*; import static java.nio.file.StandardOpenOption.*;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
@ -34,6 +36,7 @@ public abstract class AbstractPpTest {
RecordingManager recordingManager; RecordingManager recordingManager;
MockedStatic<Config> configStatic; MockedStatic<Config> configStatic;
ModelNotesService modelNotesService;
@BeforeEach @BeforeEach
public void setup() throws IOException { public void setup() throws IOException {
@ -48,6 +51,8 @@ public abstract class AbstractPpTest {
Files.writeString(postProcessed.toPath(), "foobar", CREATE_NEW, WRITE, TRUNCATE_EXISTING); Files.writeString(postProcessed.toPath(), "foobar", CREATE_NEW, WRITE, TRUNCATE_EXISTING);
Files.createDirectories(originalDir.toPath()); Files.createDirectories(originalDir.toPath());
FileUtils.touch(new File(originalDir, "playlist.m3u8")); FileUtils.touch(new File(originalDir, "playlist.m3u8"));
modelNotesService = mock(ModelNotesService.class);
when(modelNotesService.loadModelNotes(any())).thenReturn(Optional.of("tag, foo, bar"));
} }
@AfterEach @AfterEach
@ -63,7 +68,6 @@ public abstract class AbstractPpTest {
Config config = mock(Config.class); Config config = mock(Config.class);
Settings settings = mockSettings(); Settings settings = mockSettings();
when(config.getSettings()).thenReturn(settings); when(config.getSettings()).thenReturn(settings);
when(config.getModelNotes(any())).thenReturn("tag, foo, bar");
when(config.getConfigDir()).thenReturn(new File(baseDir.toFile(), "config")); when(config.getConfigDir()).thenReturn(new File(baseDir.toFile(), "config"));
configStatic = mockStatic(Config.class); configStatic = mockStatic(Config.class);
configStatic.when(Config::getInstance).thenReturn(config); configStatic.when(Config::getInstance).thenReturn(config);

View File

@ -4,6 +4,7 @@ import ctbrec.Config;
import ctbrec.Model; import ctbrec.Model;
import ctbrec.Settings; import ctbrec.Settings;
import ctbrec.UnknownModel; import ctbrec.UnknownModel;
import ctbrec.notes.ModelNotesService;
import ctbrec.recorder.Recorder; import ctbrec.recorder.Recorder;
import ctbrec.sites.chaturbate.Chaturbate; import ctbrec.sites.chaturbate.Chaturbate;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
@ -11,6 +12,9 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import java.io.IOException;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
@ -21,8 +25,10 @@ class ModelVariableExpanderTest {
Config config; Config config;
MockedStatic<Config> configStatic; MockedStatic<Config> configStatic;
ModelNotesService modelNotesService;
@BeforeEach @BeforeEach
void setup() { void setup() throws IOException {
UnknownModel unknownModel = new UnknownModel(); UnknownModel unknownModel = new UnknownModel();
Chaturbate chaturbate = new Chaturbate(); Chaturbate chaturbate = new Chaturbate();
unknownModel.setName("Pussy_Galore"); unknownModel.setName("Pussy_Galore");
@ -30,6 +36,8 @@ class ModelVariableExpanderTest {
unknownModel.setSite(chaturbate); unknownModel.setSite(chaturbate);
this.model = unknownModel; this.model = unknownModel;
this.config = mockConfig(); this.config = mockConfig();
this.modelNotesService = mock(ModelNotesService.class);
when(modelNotesService.loadModelNotes(any())).thenReturn(Optional.of("tag, foo, bar"));
} }
@AfterEach @AfterEach
@ -43,7 +51,7 @@ class ModelVariableExpanderTest {
@Test @Test
void testMultipleVariablesAsParameter() { void testMultipleVariablesAsParameter() {
Recorder recorder = mock(Recorder.class); Recorder recorder = mock(Recorder.class);
ModelVariableExpander modelVariableExpander = new ModelVariableExpander(model, config, recorder, null); ModelVariableExpander modelVariableExpander = new ModelVariableExpander(model, modelNotesService, recorder, null);
assertEquals("pussy_galore asdf pussy_galore", modelVariableExpander.expand("$lower(${modelName} ASDF ${modelName})")); assertEquals("pussy_galore asdf pussy_galore", modelVariableExpander.expand("$lower(${modelName} ASDF ${modelName})"));
} }
@ -51,7 +59,6 @@ class ModelVariableExpanderTest {
Config config = mock(Config.class); Config config = mock(Config.class);
Settings settings = mockSettings(); Settings settings = mockSettings();
when(config.getSettings()).thenReturn(settings); when(config.getSettings()).thenReturn(settings);
when(config.getModelNotes(any())).thenReturn("tag, foo, bar");
configStatic = mockStatic(Config.class); configStatic = mockStatic(Config.class);
configStatic.when(Config::getInstance).thenReturn(config); configStatic.when(Config::getInstance).thenReturn(config);
return config; return config;

View File

@ -14,10 +14,12 @@ import java.io.IOException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import static java.nio.charset.StandardCharsets.UTF_8;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
public abstract class AbstractCtbrecServlet extends HttpServlet { public abstract class AbstractCtbrecServlet extends HttpServlet {
public static final String INTERNAL_SERVER_ERROR = "Internal Server Error";
protected static final String HMAC_ERROR_DOCUMENT = "{\"status\": \"error\", \"msg\": \"HMAC does not match\"}";
private static final Logger LOG = LoggerFactory.getLogger(AbstractCtbrecServlet.class); private static final Logger LOG = LoggerFactory.getLogger(AbstractCtbrecServlet.class);
boolean checkAuthentication(HttpServletRequest req, String body) throws InvalidKeyException, NoSuchAlgorithmException { boolean checkAuthentication(HttpServletRequest req, String body) throws InvalidKeyException, NoSuchAlgorithmException {
@ -93,6 +95,7 @@ public abstract class AbstractCtbrecServlet extends HttpServlet {
void sendResponse(HttpServletResponse resp, int httpStatus, String message) { void sendResponse(HttpServletResponse resp, int httpStatus, String message) {
try { try {
resp.setStatus(httpStatus); resp.setStatus(httpStatus);
resp.setCharacterEncoding(UTF_8.toString());
resp.getWriter().print(message); resp.getWriter().print(message);
} catch (IOException e) { } catch (IOException e) {
LOG.error("Couldn't write response", e); LOG.error("Couldn't write response", e);

View File

@ -237,6 +237,10 @@ public class HttpServer {
holder.getRegistration().setMultipartConfig(multipartConfig); holder.getRegistration().setMultipartConfig(multipartConfig);
defaultContext.addServlet(holder, ImageServlet.BASE_URL + "/*"); defaultContext.addServlet(holder, ImageServlet.BASE_URL + "/*");
ModelServlet modelServlet = new ModelServlet(config);
holder = new ServletHolder(modelServlet);
defaultContext.addServlet(holder, ModelServlet.BASE_URL + "/*");
if (this.config.getSettings().webinterface) { if (this.config.getSettings().webinterface) {
startWebInterface(defaultContext, basicAuthContext); startWebInterface(defaultContext, basicAuthContext);
} }

View File

@ -22,8 +22,6 @@ import static javax.servlet.http.HttpServletResponse.*;
public class ImageServlet extends AbstractCtbrecServlet { public class ImageServlet extends AbstractCtbrecServlet {
public static final String BASE_URL = "/image"; public static final String BASE_URL = "/image";
public static final String INTERNAL_SERVER_ERROR = "Internal Server Error";
private static final String HMAC_ERROR_DOCUMENT = "{\"status\": \"error\", \"msg\": \"HMAC does not match\"}";
private static final Pattern URL_PATTERN_PORTRAIT_BY_ID = Pattern.compile(BASE_URL + "/portrait/([0-9a-fA-F]{8}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{12})"); private static final Pattern URL_PATTERN_PORTRAIT_BY_ID = Pattern.compile(BASE_URL + "/portrait/([0-9a-fA-F]{8}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{12})");
private static final Pattern URL_PATTERN_PORTRAIT_BY_URL = Pattern.compile(BASE_URL + "/portrait/url/(.*)"); private static final Pattern URL_PATTERN_PORTRAIT_BY_URL = Pattern.compile(BASE_URL + "/portrait/url/(.*)");
private final PortraitStore portraitStore; private final PortraitStore portraitStore;
@ -31,7 +29,7 @@ public class ImageServlet extends AbstractCtbrecServlet {
@Override @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) { protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
String requestURI = req.getRequestURI(); String requestURI = req.getRequestURI().substring(req.getContextPath().length());
try { try {
boolean authenticated = checkAuthentication(req, ""); boolean authenticated = checkAuthentication(req, "");
if (!authenticated) { if (!authenticated) {
@ -55,7 +53,6 @@ public class ImageServlet extends AbstractCtbrecServlet {
} }
private void servePortrait(HttpServletResponse resp, String portraitId) throws IOException { private void servePortrait(HttpServletResponse resp, String portraitId) throws IOException {
log.debug("serving portrait {}", portraitId);
Optional<byte[]> imageData = portraitStore.loadModelPortraitById(portraitId); Optional<byte[]> imageData = portraitStore.loadModelPortraitById(portraitId);
if (imageData.isPresent()) { if (imageData.isPresent()) {
resp.setStatus(SC_OK); resp.setStatus(SC_OK);
@ -71,7 +68,7 @@ public class ImageServlet extends AbstractCtbrecServlet {
@Override @Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) { protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
String requestURI = req.getRequestURI(); String requestURI = req.getRequestURI().substring(req.getContextPath().length());
try { try {
byte[] data = bodyAsByteArray(req); byte[] data = bodyAsByteArray(req);
boolean authenticated = checkAuthentication(req, data); boolean authenticated = checkAuthentication(req, data);
@ -93,7 +90,7 @@ public class ImageServlet extends AbstractCtbrecServlet {
@Override @Override
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) { protected void doDelete(HttpServletRequest req, HttpServletResponse resp) {
String requestURI = req.getRequestURI(); String requestURI = req.getRequestURI().substring(req.getContextPath().length());
try { try {
boolean authenticated = checkAuthentication(req, ""); boolean authenticated = checkAuthentication(req, "");
if (!authenticated) { if (!authenticated) {

View File

@ -0,0 +1,101 @@
package ctbrec.recorder.server;
import ctbrec.Config;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONObject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URLDecoder;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.nio.charset.StandardCharsets.UTF_8;
import static javax.servlet.http.HttpServletResponse.*;
@Slf4j
@RequiredArgsConstructor
public class ModelServlet extends AbstractCtbrecServlet {
public static final String BASE_URL = "/models";
private static final Pattern URL_PATTERN_ALL_MODEL_NOTES = Pattern.compile(BASE_URL + "/notes/?");
private static final Pattern URL_PATTERN_MODEL_NOTES = Pattern.compile(BASE_URL + "/notes/(.+?)");
private final Config config;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
String requestURI = req.getRequestURI().substring(req.getContextPath().length());
try {
boolean authenticated = checkAuthentication(req, "");
if (!authenticated) {
sendResponse(resp, SC_UNAUTHORIZED, HMAC_ERROR_DOCUMENT);
return;
}
Matcher m;
if ((m = URL_PATTERN_MODEL_NOTES.matcher(requestURI)).matches()) {
String modelUrl = URLDecoder.decode(m.group(1), UTF_8);
String notes = config.getSettings().modelNotes.getOrDefault(modelUrl, "");
log.debug("Model Notes Request {} - {}", modelUrl, notes);
resp.setContentType("text/plain");
sendResponse(resp, SC_OK, notes);
} else if ((URL_PATTERN_ALL_MODEL_NOTES.matcher(requestURI)).matches()) {
JSONObject notes = new JSONObject();
config.getSettings().modelNotes.forEach(notes::put);
resp.setContentType("application/json");
sendResponse(resp, SC_OK, notes.toString());
}
} catch (Exception e) {
log.error(INTERNAL_SERVER_ERROR, e);
sendResponse(resp, SC_INTERNAL_SERVER_ERROR, INTERNAL_SERVER_ERROR);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
String requestURI = req.getRequestURI().substring(req.getContextPath().length());
try {
var body = body(req);
log.info("Notes: [{}]", body);
boolean authenticated = checkAuthentication(req, body);
if (!authenticated) {
sendResponse(resp, SC_UNAUTHORIZED, HMAC_ERROR_DOCUMENT);
return;
}
Matcher m;
if ((m = URL_PATTERN_MODEL_NOTES.matcher(requestURI)).matches()) {
String modelUrl = URLDecoder.decode(m.group(1), UTF_8);
config.getSettings().modelNotes.put(modelUrl, body);
config.save();
}
} catch (Exception e) {
log.error(INTERNAL_SERVER_ERROR, e);
sendResponse(resp, SC_INTERNAL_SERVER_ERROR, INTERNAL_SERVER_ERROR);
}
}
@Override
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) {
String requestURI = req.getRequestURI().substring(req.getContextPath().length());
try {
boolean authenticated = checkAuthentication(req, "");
if (!authenticated) {
sendResponse(resp, SC_UNAUTHORIZED, HMAC_ERROR_DOCUMENT);
return;
}
Matcher m;
if ((m = URL_PATTERN_MODEL_NOTES.matcher(requestURI)).matches()) {
String modelUrl = URLDecoder.decode(m.group(1), UTF_8);
config.getSettings().modelNotes.remove(modelUrl);
config.save();
}
} catch (Exception e) {
log.error(INTERNAL_SERVER_ERROR, e);
sendResponse(resp, SC_INTERNAL_SERVER_ERROR, INTERNAL_SERVER_ERROR);
}
}
}