Add variable support for model variable to player params
This commit is contained in:
parent
45a385be11
commit
ed5d674be3
|
@ -3,6 +3,13 @@
|
|||
* Fix LiveJasmin followed tab
|
||||
* Add buttons to settings to delete cookies per site
|
||||
* Fix bug in minimal browser
|
||||
* Model placeholders can now be used for player params
|
||||
${modelName}
|
||||
${modelDisplayName}
|
||||
${modelSanitizedName}
|
||||
${modelNotes}
|
||||
${siteName}
|
||||
${siteSanitizedName}
|
||||
|
||||
4.5.3
|
||||
========================
|
||||
|
|
|
@ -25,12 +25,14 @@ import ctbrec.Config;
|
|||
import ctbrec.Model;
|
||||
import ctbrec.OS;
|
||||
import ctbrec.Recording;
|
||||
import ctbrec.StringUtil;
|
||||
import ctbrec.event.EventBusHolder;
|
||||
import ctbrec.io.StreamRedirector;
|
||||
import ctbrec.io.UrlUtil;
|
||||
import ctbrec.recorder.download.StreamSource;
|
||||
import ctbrec.ui.controls.Dialogs;
|
||||
import ctbrec.ui.event.PlayerStartedEvent;
|
||||
import ctbrec.variableexpansion.ModelVariableExpander;
|
||||
import javafx.scene.Scene;
|
||||
|
||||
public class Player {
|
||||
|
@ -41,24 +43,6 @@ public class Player {
|
|||
private Player() {
|
||||
}
|
||||
|
||||
private static boolean play(String url, boolean async) {
|
||||
boolean singlePlayer = Config.getInstance().getSettings().singlePlayer;
|
||||
try {
|
||||
if (singlePlayer && playerThread != null && playerThread.isRunning()) {
|
||||
playerThread.stopThread();
|
||||
}
|
||||
|
||||
playerThread = new PlayerThread(url);
|
||||
if (!async) {
|
||||
playerThread.join();
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e1) {
|
||||
LOG.error("Couldn't start player", e1);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean play(Recording rec) {
|
||||
boolean singlePlayer = Config.getInstance().getSettings().singlePlayer;
|
||||
try {
|
||||
|
@ -85,44 +69,34 @@ public class Player {
|
|||
if (singlePlayer && playerThread != null && playerThread.isRunning()) {
|
||||
playerThread.stopThread();
|
||||
}
|
||||
String playlistUrl = getPlaylistUrl(model);
|
||||
LOG.debug("Playing {}", playlistUrl);
|
||||
|
||||
EventBusHolder.BUS.post(new PlayerStartedEvent(model));
|
||||
return Player.play(playlistUrl, async);
|
||||
|
||||
if (singlePlayer && playerThread != null && playerThread.isRunning()) {
|
||||
playerThread.stopThread();
|
||||
}
|
||||
|
||||
playerThread = new PlayerThread(model);
|
||||
if (!async) {
|
||||
playerThread.join();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
Dialogs.showError(scene, "Room not public", "Room is currently not public", null);
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e1) {
|
||||
LOG.error("Couldn't get stream information for model {}", model, e1);
|
||||
Dialogs.showError(scene, "Couldn't determine stream URL", e1.getLocalizedMessage(), e1);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
LOG.error("Couldn't get stream information for model {}", model, e);
|
||||
Dialogs.showError(scene, "Couldn't determine stream URL", e.getLocalizedMessage(), e);
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
LOG.error("Couldn't get stream information for model {}", model, e);
|
||||
Dialogs.showError(scene, "Couldn't determine stream URL", e.getLocalizedMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getPlaylistUrl(Model model) throws IOException, ExecutionException, ParseException, PlaylistException, JAXBException {
|
||||
List<StreamSource> sources = model.getStreamSources();
|
||||
Collections.sort(sources);
|
||||
StreamSource best;
|
||||
int maxRes = Config.getInstance().getSettings().maximumResolutionPlayer;
|
||||
if (maxRes > 0 && !sources.isEmpty()) {
|
||||
for (Iterator<StreamSource> iterator = sources.iterator(); iterator.hasNext();) {
|
||||
StreamSource streamSource = iterator.next();
|
||||
if (streamSource.height > 0 && maxRes < streamSource.height) {
|
||||
LOG.trace("Res too high {} > {}", streamSource.height, maxRes);
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sources.isEmpty()) {
|
||||
throw new RuntimeException("No stream left in playlist, because player resolution is set to " + maxRes);
|
||||
} else {
|
||||
LOG.debug("{} selected {}", model.getName(), sources.get(sources.size() - 1));
|
||||
best = sources.get(sources.size() - 1);
|
||||
}
|
||||
return best.getMediaPlaylistUrl();
|
||||
}
|
||||
|
||||
public static void stop() {
|
||||
if (playerThread != null) {
|
||||
playerThread.stopThread();
|
||||
|
@ -132,11 +106,11 @@ public class Player {
|
|||
private static class PlayerThread extends Thread {
|
||||
private boolean running = false;
|
||||
private Process playerProcess;
|
||||
private String url;
|
||||
private Recording rec;
|
||||
private Model model;
|
||||
|
||||
PlayerThread(String url) {
|
||||
this.url = url;
|
||||
PlayerThread(Model model) {
|
||||
this.model = model;
|
||||
setName(getClass().getName());
|
||||
start();
|
||||
}
|
||||
|
@ -158,11 +132,16 @@ public class Player {
|
|||
String[] cmdline = createCmdline(file.getAbsolutePath());
|
||||
playerProcess = rt.exec(cmdline, OS.getEnvironment(), file.getParentFile());
|
||||
} else {
|
||||
String url = null;
|
||||
if (rec != null) {
|
||||
url = getRemoteRecordingUrl(rec, cfg);
|
||||
} else if (model != null) {
|
||||
url = getPlaylistUrl(model);
|
||||
}
|
||||
LOG.debug("Playing {}", url);
|
||||
String[] cmdline = createCmdline(url);
|
||||
expandPlaceHolders(cmdline);
|
||||
LOG.debug("Player command line: {}", Arrays.toString(cmdline));
|
||||
playerProcess = rt.exec(cmdline);
|
||||
}
|
||||
|
||||
|
@ -179,6 +158,10 @@ public class Player {
|
|||
|
||||
playerProcess.waitFor();
|
||||
LOG.debug("Media player finished.");
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
LOG.error("Error in player thread", e);
|
||||
Dialogs.showError(scene, "Playback failed", "Couldn't start playback", e);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error in player thread", e);
|
||||
Dialogs.showError(scene, "Playback failed", "Couldn't start playback", e);
|
||||
|
@ -186,20 +169,52 @@ public class Player {
|
|||
running = false;
|
||||
}
|
||||
|
||||
private static String getPlaylistUrl(Model model) throws IOException, ExecutionException, ParseException, PlaylistException, JAXBException {
|
||||
List<StreamSource> sources = model.getStreamSources();
|
||||
Collections.sort(sources);
|
||||
StreamSource best;
|
||||
int maxRes = Config.getInstance().getSettings().maximumResolutionPlayer;
|
||||
if (maxRes > 0 && !sources.isEmpty()) {
|
||||
for (Iterator<StreamSource> iterator = sources.iterator(); iterator.hasNext();) {
|
||||
StreamSource streamSource = iterator.next();
|
||||
if (streamSource.height > 0 && maxRes < streamSource.height) {
|
||||
LOG.trace("Res too high {} > {}", streamSource.height, maxRes);
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sources.isEmpty()) {
|
||||
throw new RuntimeException("No stream left in playlist, because player resolution is set to " + maxRes);
|
||||
} else {
|
||||
LOG.debug("{} selected {}", model.getName(), sources.get(sources.size() - 1));
|
||||
best = sources.get(sources.size() - 1);
|
||||
}
|
||||
return best.getMediaPlaylistUrl();
|
||||
}
|
||||
|
||||
private void expandPlaceHolders(String[] cmdline) {
|
||||
ModelVariableExpander expander = new ModelVariableExpander(model, Config.getInstance(), null);
|
||||
for (int i = 0; i < cmdline.length; i++) {
|
||||
var param = cmdline[i];
|
||||
param = expander.expand(param);
|
||||
cmdline[i] = param;
|
||||
}
|
||||
}
|
||||
|
||||
private String[] createCmdline(String mediaSource) {
|
||||
Config cfg = Config.getInstance();
|
||||
String params = cfg.getSettings().mediaPlayerParams.trim();
|
||||
|
||||
String[] cmdline = null;
|
||||
if(!params.isEmpty()) {
|
||||
String[] playerArgs = params.split(" ");
|
||||
if (params.isEmpty()) {
|
||||
cmdline = new String[2];
|
||||
} else {
|
||||
String[] playerArgs = StringUtil.splitParams(params);
|
||||
cmdline = new String[playerArgs.length + 2];
|
||||
System.arraycopy(playerArgs, 0, cmdline, 1, playerArgs.length);
|
||||
} else {
|
||||
cmdline = new String[2];
|
||||
}
|
||||
cmdline[0] = cfg.getSettings().mediaPlayer;
|
||||
cmdline[cmdline.length - 1] = mediaSource;
|
||||
LOG.debug("Player command line: {}", Arrays.toString(cmdline));
|
||||
return cmdline;
|
||||
}
|
||||
|
||||
|
|
|
@ -225,20 +225,25 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
var storage = new CtbrecPreferencesStorage(config);
|
||||
var prefs = Preferences.of(storage,
|
||||
Category.of("General",
|
||||
Group.of("General", Setting.of("User-Agent", httpUserAgent),
|
||||
Group.of("General",
|
||||
Setting.of("User-Agent", httpUserAgent),
|
||||
Setting.of("User-Agent mobile", httpUserAgentMobile),
|
||||
Setting.of("Update overview interval (seconds)", overviewUpdateIntervalInSecs, "Update the thumbnail overviews every x seconds").needsRestart(),
|
||||
Setting.of("Update thumbnails", updateThumbnails,
|
||||
"The overviews will still be updated, but the thumbnails won't be changed. This is useful for less powerful systems."),
|
||||
Setting.of("Manually select stream quality", chooseStreamQuality, "Opens a dialog to select the video resolution before recording"),
|
||||
Setting.of("Enable live previews (experimental)", livePreviews), Setting.of("Enable recently watched tab", recentlyWatched).needsRestart(),
|
||||
Setting.of("Enable live previews (experimental)", livePreviews),
|
||||
Setting.of("Enable recently watched tab", recentlyWatched).needsRestart(),
|
||||
Setting.of("Minimize to tray", minimizeToTray, "Removes the app from the task bar, if minimized"),
|
||||
Setting.of("Add models from clipboard", monitorClipboard, "Monitor clipboard for model URLs and automatically add them to the recorder").needsRestart(),
|
||||
Setting.of("Show confirmation dialogs", confirmationDialogs, "Show confirmation dialogs for irreversible actions"),
|
||||
Setting.of("Start Tab", startTab)),
|
||||
Group.of("Player", Setting.of("Player", mediaPlayer), Setting.of("Start parameters", mediaPlayerParams),
|
||||
Group.of("Player",
|
||||
Setting.of("Player", mediaPlayer),
|
||||
Setting.of("Start parameters", mediaPlayerParams),
|
||||
Setting.of("Maximum resolution (0 = unlimited)", maximumResolutionPlayer, "video height, e.g. 720 or 1080"),
|
||||
Setting.of("Show \"Player Starting\" Message", showPlayerStarting), Setting.of("Start only one player at a time", singlePlayer))),
|
||||
Setting.of("Show \"Player Starting\" Message", showPlayerStarting),
|
||||
Setting.of("Start only one player at a time", singlePlayer))),
|
||||
Category.of("Look & Feel",
|
||||
Group.of("Look & Feel",
|
||||
Setting.of("Colors (Base / Accent)", new ColorSettingsPane(Config.getInstance())).needsRestart(),
|
||||
|
@ -248,10 +253,11 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
Setting.of("Show grid lines in tables", showGridLinesInTables, "Show grid lines in tables").needsRestart(),
|
||||
Setting.of("Fast scroll speed", fastScrollSpeed, "Makes the thumbnail overviews scroll faster with the mouse wheel").needsRestart())),
|
||||
Category.of("Recorder",
|
||||
Group.of("Recorder", Setting.of("Recordings Directory", recordingsDir), Setting.of("Directory Structure", directoryStructure),
|
||||
Group.of("Recorder",
|
||||
Setting.of("Recordings Directory", recordingsDir),
|
||||
Setting.of("Directory Structure", directoryStructure),
|
||||
Setting.of("Split recordings after", splitAfter).converter(SplitAfterOption.converter()).onChange(this::splitValuesChanged),
|
||||
Setting.of("Split recordings bigger than", splitBiggerThan).converter(SplitBiggerThanOption.converter())
|
||||
.onChange(this::splitValuesChanged),
|
||||
Setting.of("Split recordings bigger than", splitBiggerThan).converter(SplitBiggerThanOption.converter()).onChange(this::splitValuesChanged),
|
||||
Setting.of("Restrict Resolution", resolutionRange, "Only record streams with resolution within the given range"),
|
||||
Setting.of("Concurrent Recordings (0 = unlimited)", concurrentRecordings),
|
||||
Setting.of("Default Priority", defaultPriority),
|
||||
|
@ -265,23 +271,34 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
Setting.of("Don't record from", timeoutRecordingStartingAt),
|
||||
Setting.of("Until", timeoutRecordingEndingAt)
|
||||
),
|
||||
Group.of("Location", Setting.of("Record Location", recordLocal).needsRestart(), Setting.of("Server", server), Setting.of("Port", port),
|
||||
Group.of("Location",
|
||||
Setting.of("Record Location", recordLocal).needsRestart(),
|
||||
Setting.of("Server", server),
|
||||
Setting.of("Port", port),
|
||||
Setting.of("Path", path, "Leave empty, if you didn't change the servletContext in the server config"),
|
||||
Setting.of("Download Filename", downloadFilename, "File name pattern for downloads"), Setting.of("", variablesHelpButton),
|
||||
Setting.of("Download Filename", downloadFilename, "File name pattern for downloads"),
|
||||
Setting.of("", variablesHelpButton),
|
||||
Setting.of("Require authentication", requireAuthentication),
|
||||
Setting.of("Use Secure Communication (TLS)", transportLayerSecurity))),
|
||||
Category.of("Post-Processing",
|
||||
Group.of("Post-Processing", Setting.of("Threads", postProcessingThreads), Setting.of("Steps", postProcessingStepPanel),
|
||||
Group.of("Post-Processing",
|
||||
Setting.of("Threads", postProcessingThreads),
|
||||
Setting.of("Steps", postProcessingStepPanel),
|
||||
Setting.of("", createHelpButton("Post-Processing Help", "http://localhost:5689/docs/PostProcessing.md")))),
|
||||
Category.of("Events & Actions", new ActionSettingsPanel(recorder)), Category.of("Ignore List", ignoreList),
|
||||
Category.of("Sites", siteCategories.toArray(new Category[0])),
|
||||
Category.of("Proxy",
|
||||
Group.of("Proxy", Setting.of("Type", proxyType).needsRestart(), Setting.of("Host", proxyHost).needsRestart(),
|
||||
Setting.of("Port", proxyPort).needsRestart(), Setting.of("Username", proxyUser).needsRestart(),
|
||||
Group.of("Proxy",
|
||||
Setting.of("Type", proxyType).needsRestart(),
|
||||
Setting.of("Host", proxyHost).needsRestart(),
|
||||
Setting.of("Port", proxyPort).needsRestart(),
|
||||
Setting.of("Username", proxyUser).needsRestart(),
|
||||
Setting.of("Password", proxyPassword).needsRestart())),
|
||||
Category.of("Advanced / Devtools",
|
||||
Group.of("Networking", Setting.of("Playlist request timeout (ms)", playlistRequestTimeout, "Timeout in ms for playlist requests")),
|
||||
Group.of("Logging", Setting.of("Log FFmpeg output", logFFmpegOutput, "Log FFmpeg output to files in the system's temp directory"),
|
||||
Group.of("Networking",
|
||||
Setting.of("Playlist request timeout (ms)", playlistRequestTimeout, "Timeout in ms for playlist requests")),
|
||||
Group.of("Logging",
|
||||
Setting.of("Log FFmpeg output", logFFmpegOutput, "Log FFmpeg output to files in the system's temp directory"),
|
||||
Setting.of("Log missed segments", logMissedSegments,
|
||||
"Write a log files in the system's temp directory to analyze missed segments")),
|
||||
Group.of("hlsdl (experimental)",
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package ctbrec;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class StringUtil {
|
||||
|
@ -191,4 +194,18 @@ public class StringUtil {
|
|||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
public static String[] splitParams(String params) {
|
||||
Pattern p = Pattern.compile("(\"[^\"]+\"|[^\\s\"]+)");
|
||||
Matcher m = p.matcher(params);
|
||||
List<String> result = new LinkedList<>();
|
||||
while (m.find()) {
|
||||
String group = m.group();
|
||||
if (group.startsWith("\"") && group.endsWith("\"")) {
|
||||
group = group.substring(1, group.length() - 1);
|
||||
}
|
||||
result.add(group);
|
||||
}
|
||||
return result.toArray(new String[0]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,38 +12,31 @@ import java.util.Locale;
|
|||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.ModelGroup;
|
||||
import ctbrec.Recording;
|
||||
import ctbrec.StringUtil;
|
||||
import ctbrec.sites.Site;
|
||||
import ctbrec.variableexpansion.ModelVariableExpander;
|
||||
|
||||
public abstract class AbstractPlaceholderAwarePostProcessor extends AbstractPostProcessor {
|
||||
|
||||
public String fillInPlaceHolders(String input, PostProcessingContext ctx) {
|
||||
Recording rec = ctx.getRecording();
|
||||
Config config = ctx.getConfig();
|
||||
Optional<ModelGroup> modelGroup = Optional.ofNullable(ctx.getRecorder()).flatMap(r -> r.getModelGroup(rec.getModel()));
|
||||
|
||||
Map<String, Function<String, Optional<String>>> placeholderValueSuppliers = new HashMap<>();
|
||||
placeholderValueSuppliers.put("modelName", r -> ofNullable(rec.getModel().getName()));
|
||||
placeholderValueSuppliers.put("modelDisplayName", r -> ofNullable(rec.getModel().getDisplayName()));
|
||||
placeholderValueSuppliers.put("modelSanitizedName", r -> getSanitizedName(rec.getModel()));
|
||||
placeholderValueSuppliers.put("siteName", r -> ofNullable(rec.getModel().getSite()).map(Site::getName));
|
||||
placeholderValueSuppliers.put("siteSanitizedName", r -> getSanitizedSiteName(rec));
|
||||
placeholderValueSuppliers.put("fileSuffix", r -> getFileSuffix(rec));
|
||||
placeholderValueSuppliers.put("epochSecond", r -> ofNullable(rec.getStartDate()).map(Instant::getEpochSecond).map(l -> Long.toString(l))); // NOSONAR
|
||||
placeholderValueSuppliers.put("modelNotes", r -> getSanitizedModelNotes(config, rec.getModel()));
|
||||
|
||||
ModelVariableExpander modelExpander = new ModelVariableExpander(rec.getModel(), config, ctx.getRecorder());
|
||||
placeholderValueSuppliers.putAll(modelExpander.getPlaceholderValueSuppliers());
|
||||
|
||||
placeholderValueSuppliers.put("recordingNotes", r -> getSanitizedRecordingNotes(rec));
|
||||
|
||||
placeholderValueSuppliers.put("fileSuffix", r -> getFileSuffix(rec));
|
||||
placeholderValueSuppliers.put("recordingsDir", r -> Optional.of(config.getSettings().recordingsDir));
|
||||
placeholderValueSuppliers.put("absolutePath", r -> Optional.of(rec.getPostProcessedFile().getAbsolutePath()));
|
||||
placeholderValueSuppliers.put("absoluteParentPath", r -> Optional.of(rec.getPostProcessedFile().getParentFile().getAbsolutePath()));
|
||||
placeholderValueSuppliers.put("modelGroupName", r -> modelGroup.map(ModelGroup::getName));
|
||||
placeholderValueSuppliers.put("modelGroupId", r -> modelGroup.map(ModelGroup::getId).map(UUID::toString));
|
||||
|
||||
placeholderValueSuppliers.put("epochSecond", r -> ofNullable(rec.getStartDate()).map(Instant::getEpochSecond).map(l -> Long.toString(l))); // NOSONAR
|
||||
placeholderValueSuppliers.put("utcDateTime", pattern -> replaceUtcDateTime(rec, pattern));
|
||||
placeholderValueSuppliers.put("localDateTime", pattern -> replaceLocalDateTime(rec, pattern));
|
||||
|
||||
|
@ -51,15 +44,6 @@ public abstract class AbstractPlaceholderAwarePostProcessor extends AbstractPost
|
|||
return output;
|
||||
}
|
||||
|
||||
private Optional<String> getSanitizedName(Model model) {
|
||||
String name = model.getSanitizedNamed();
|
||||
if (StringUtil.isBlank(name)) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return Optional.of(name);
|
||||
}
|
||||
}
|
||||
|
||||
private String fillInPlaceHolders(String input, Map<String, Function<String, Optional<String>>> placeholderValueSuppliers) {
|
||||
boolean somethingReplaced = false;
|
||||
do {
|
||||
|
@ -89,7 +73,7 @@ public abstract class AbstractPlaceholderAwarePostProcessor extends AbstractPost
|
|||
Optional<String> optionalValue = placeholderValueSuppliers.getOrDefault(name, r -> Optional.of(name)).apply(expression);
|
||||
String value = optionalValue.orElse(defaultValue);
|
||||
StringBuilder sb = new StringBuilder(input);
|
||||
String output = sb.replace(start, end+1, value).toString();
|
||||
String output = sb.replace(start, end + 1, value).toString();
|
||||
somethingReplaced = !Objects.equals(input, output);
|
||||
input = output;
|
||||
}
|
||||
|
@ -128,15 +112,6 @@ public abstract class AbstractPlaceholderAwarePostProcessor extends AbstractPost
|
|||
}
|
||||
}
|
||||
|
||||
private Optional<String> getSanitizedSiteName(Recording rec) {
|
||||
Optional<String> name = ofNullable(rec.getModel().getSite()).map(Site::getName);
|
||||
if (name.isPresent()) {
|
||||
return Optional.of(sanitize(name.get()));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<String> getSanitizedRecordingNotes(Recording rec) {
|
||||
Optional<String> notes = ofNullable(rec.getNote());
|
||||
if (notes.isPresent()) {
|
||||
|
@ -145,13 +120,4 @@ public abstract class AbstractPlaceholderAwarePostProcessor extends AbstractPost
|
|||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<String> getSanitizedModelNotes(Config config, Model m) {
|
||||
Optional<String> notes = ofNullable(config.getModelNotes(m));
|
||||
if (notes.isPresent()) {
|
||||
return Optional.of(sanitize(notes.get()));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
package ctbrec.variableexpansion;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
abstract class AbstractVariableExpander {
|
||||
|
||||
protected Map<String, Function<String, Optional<String>>> placeholderValueSuppliers = new HashMap<>();
|
||||
|
||||
protected String fillInPlaceHolders(String input, Map<String, Function<String, Optional<String>>> placeholderValueSuppliers) {
|
||||
boolean somethingReplaced = false;
|
||||
do {
|
||||
somethingReplaced = false;
|
||||
int end = input.indexOf("}");
|
||||
if (end > 0) {
|
||||
int start = input.substring(0, end).lastIndexOf("${");
|
||||
if (start >= 0) {
|
||||
String placeholder = input.substring(start, end + 1);
|
||||
String placeholderName = placeholder.substring(2, placeholder.length() - 1);
|
||||
String defaultValue = null;
|
||||
String expression = null;
|
||||
int questionMark = placeholder.indexOf('?');
|
||||
if (questionMark > 0) {
|
||||
placeholderName = placeholder.substring(2, questionMark);
|
||||
defaultValue = placeholder.substring(questionMark + 1, placeholder.length() - 1);
|
||||
} else {
|
||||
defaultValue = "";
|
||||
}
|
||||
int bracket = placeholder.indexOf('(');
|
||||
if (bracket > 0) {
|
||||
placeholderName = placeholder.substring(2, bracket);
|
||||
expression = placeholder.substring(bracket + 1, placeholder.indexOf(')', bracket));
|
||||
}
|
||||
|
||||
final String name = placeholderName;
|
||||
Optional<String> optionalValue = placeholderValueSuppliers.getOrDefault(name, r -> Optional.of(name)).apply(expression);
|
||||
String value = optionalValue.orElse(defaultValue);
|
||||
StringBuilder sb = new StringBuilder(input);
|
||||
String output = sb.replace(start, end+1, value).toString();
|
||||
somethingReplaced = !Objects.equals(input, output);
|
||||
input = output;
|
||||
}
|
||||
}
|
||||
} while (somethingReplaced);
|
||||
return input;
|
||||
}
|
||||
|
||||
public Set<String> getPlaceholderNames() {
|
||||
return placeholderValueSuppliers.keySet();
|
||||
}
|
||||
|
||||
public Map<String, Function<String, Optional<String>>> getPlaceholderValueSuppliers() {
|
||||
return placeholderValueSuppliers;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package ctbrec.variableexpansion;
|
||||
|
||||
import static ctbrec.StringUtil.*;
|
||||
import static java.util.Optional.*;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.ModelGroup;
|
||||
import ctbrec.StringUtil;
|
||||
import ctbrec.recorder.Recorder;
|
||||
import ctbrec.sites.Site;
|
||||
|
||||
public class ModelVariableExpander extends AbstractVariableExpander {
|
||||
|
||||
public ModelVariableExpander(Model model, Config config, Recorder recorder) {
|
||||
Optional<ModelGroup> modelGroup = Optional.ofNullable(recorder).flatMap(r -> r.getModelGroup(model));
|
||||
placeholderValueSuppliers.put("modelName", r -> ofNullable(model.getName()));
|
||||
placeholderValueSuppliers.put("modelDisplayName", r -> ofNullable(model.getDisplayName()));
|
||||
placeholderValueSuppliers.put("modelSanitizedName", r -> getSanitizedName(model));
|
||||
placeholderValueSuppliers.put("modelNotes", r -> getSanitizedModelNotes(config, model));
|
||||
placeholderValueSuppliers.put("siteName", r -> ofNullable(model.getSite()).map(Site::getName));
|
||||
placeholderValueSuppliers.put("siteSanitizedName", r -> getSanitizedSiteName(model));
|
||||
placeholderValueSuppliers.put("modelGroupName", r -> modelGroup.map(ModelGroup::getName));
|
||||
placeholderValueSuppliers.put("modelGroupId", r -> modelGroup.map(ModelGroup::getId).map(UUID::toString));
|
||||
}
|
||||
|
||||
public String expand(String input) {
|
||||
return fillInPlaceHolders(input, placeholderValueSuppliers);
|
||||
}
|
||||
|
||||
private Optional<String> getSanitizedName(Model model) {
|
||||
String name = model.getSanitizedNamed();
|
||||
if (StringUtil.isBlank(name)) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return Optional.of(name);
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<String> getSanitizedSiteName(Model model) {
|
||||
Optional<String> name = ofNullable(model.getSite()).map(Site::getName);
|
||||
if (name.isPresent()) {
|
||||
return Optional.of(sanitize(name.get()));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<String> getSanitizedModelNotes(Config config, Model m) {
|
||||
Optional<String> notes = ofNullable(config.getModelNotes(m));
|
||||
if (notes.isPresent()) {
|
||||
return Optional.of(sanitize(notes.get()));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue