Add mechanism to specify fallback values in the pp variables

This commit is contained in:
0xb00bface 2021-05-09 21:28:22 +02:00
parent 9bb2d5d593
commit c49a7db192
3 changed files with 156 additions and 78 deletions

View File

@ -42,11 +42,14 @@ The part you have to copy is
<a id="variables" />
###### Available variables:
- **${modelName}** - the name of the recorded model
- **${modelDisplayName}** - the name of the recorded model, which is shown on the webpage. Might be the same as
${modelName}
- **${modelSanitizedName}** - sanitized name of the model. The following characters are replaced by an underscore:
\\, /, ', " and space
- **${modelGroupName}** - name of the model group, if the model is part of a group
- **${modelGroupId}** - the unique ID of the model group, if the model is part of a group
- **${siteName}** - the name of the cam site, the model streams on
- **${siteSanitizedName}** - sanitized name of the site. The following characters are replaced by an underscore:
\\, /, ', " and space
@ -116,4 +119,15 @@ The part you have to copy is
</table>
For more information see: [DateTimeFormatter](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/format/DateTimeFormatter.html)
For more information see: [DateTimeFormatter](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/format/DateTimeFormatter.html)
###### Fallback values:
You can define a fallback value for each variable in case there is no value available for the variable. The syntax is
${placeholder?foobar}
Let's for example say you have created some model groups. For models, which are part of a group, you want to use the group name. But for models, which
are not part of a group you want to use the sanitized name. You can achieve that by using the following expression:
${modelGroupName?${modelSanitizedName}}
It can be read like "use the modelGroupName, but if that is not available use modelSanitizedName".

View File

@ -3,107 +3,153 @@ package ctbrec.recorder.postprocessing;
import static ctbrec.StringUtil.*;
import static java.util.Optional.*;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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;
public abstract class AbstractPlaceholderAwarePostProcessor extends AbstractPostProcessor {
public static final String[] PLACE_HOLDERS = {
"${modelName}",
"${modelDisplayName}",
"${modelSanitizedName}",
"${siteName}",
"${siteSanitizedName}",
"${utcDateTime}",
"${localDateTime}",
"${epochSecond}",
"${fileSuffix}",
"${modelNotes}",
"${recordingNotes}",
"${recordingsDir}",
"${absolutePath}",
"${absoluteParentPath}"
};
public String fillInPlaceHolders(String input, PostProcessingContext ctx) {
Recording rec = ctx.getRecording();
Config config = ctx.getConfig();
// @formatter:off
String output = input
.replace("${modelName}", ofNullable(rec.getModel().getName()).orElse("modelName"))
.replace("${modelDisplayName}", ofNullable(rec.getModel().getDisplayName()).orElse("displayName"))
.replace("${modelSanitizedName}", ofNullable(rec.getModel().getSanitizedNamed()).orElse("sanitizedName"))
.replace("${siteName}", ofNullable(rec.getModel().getSite()).map(Site::getName).orElse("site"))
.replace("${siteSanitizedName}", getSanitizedSiteName(rec))
.replace("${fileSuffix}", getFileSuffix(rec))
.replace("${epochSecond}", Long.toString(rec.getStartDate().getEpochSecond()))
.replace("${modelNotes}", sanitize(config.getModelNotes(rec.getModel())))
.replace("${recordingNotes}", getSanitizedRecordingNotes(rec))
.replace("${recordingsDir}", config.getSettings().recordingsDir)
.replace("${absolutePath}", rec.getPostProcessedFile().getAbsolutePath())
.replace("${absoluteParentPath}", rec.getPostProcessedFile().getParentFile().getAbsolutePath())
;
Optional<ModelGroup> modelGroup = config.getModelGroup(rec.getModel());
output = replaceUtcDateTime(rec, output);
output = replaceLocalDateTime(rec, output);
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()));
placeholderValueSuppliers.put("recordingNotes", r -> getSanitizedRecordingNotes(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("utcDateTime", pattern -> replaceUtcDateTime(rec, pattern));
placeholderValueSuppliers.put("localDateTime", pattern -> replaceLocalDateTime(rec, pattern));
String output = fillInPlaceHolders(input, placeholderValueSuppliers);
return output;
// @formatter:on
}
private String replaceUtcDateTime(Recording rec, String filename) {
return replaceDateTime(rec, filename, "utcDateTime", ZoneOffset.UTC);
}
private String replaceLocalDateTime(Recording rec, String filename) {
return replaceDateTime(rec, filename, "localDateTime", ZoneId.systemDefault());
}
private String replaceDateTime(Recording rec, String filename, String placeHolder, ZoneId zone) {
String pattern = "yyyy-MM-dd_HH-mm-ss";
Pattern regex = Pattern.compile("\\$\\{" + placeHolder + "(?:\\((.*?)\\))?\\}");
Matcher m = regex.matcher(filename);
while (m.find()) {
String p = m.group(1);
if (p != null) {
pattern = p;
}
String formattedDate = getDateTime(rec, pattern, zone);
filename = m.replaceFirst(formattedDate);
m = regex.matcher(filename);
}
return filename;
}
private String getDateTime(Recording rec, String pattern, ZoneId zone) {
return DateTimeFormatter.ofPattern(pattern)
.withLocale(Locale.getDefault())
.withZone(zone)
.format(rec.getStartDate());
}
private CharSequence getFileSuffix(Recording rec) {
if(rec.isSingleFile()) {
String filename = rec.getPostProcessedFile().getName();
return filename.substring(filename.lastIndexOf('.') + 1);
private Optional<String> getSanitizedName(Model model) {
String name = model.getSanitizedNamed();
if (StringUtil.isBlank(name)) {
return Optional.empty();
} else {
return "";
return Optional.of(name);
}
}
private CharSequence getSanitizedSiteName(Recording rec) {
return sanitize(ofNullable(rec.getModel().getSite()).map(Site::getName).orElse(""));
private 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);
}
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;
}
private CharSequence getSanitizedRecordingNotes(Recording rec) {
return sanitize(ofNullable(rec.getNote()).orElse(""));
private Optional<String> replaceUtcDateTime(Recording rec, String pattern) {
return replaceDateTime(rec, pattern, ZoneOffset.UTC);
}
private Optional<String> replaceLocalDateTime(Recording rec, String filename) {
return replaceDateTime(rec, filename, ZoneId.systemDefault());
}
private Optional<String> replaceDateTime(Recording rec, String pattern, ZoneId zone) {
pattern = pattern != null ? pattern : "yyyy-MM-dd_HH-mm-ss";
return getDateTime(rec, pattern, zone);
}
private Optional<String> getDateTime(Recording rec, String pattern, ZoneId zone) {
return Optional.ofNullable(rec.getStartDate()) //
.map(DateTimeFormatter.ofPattern(pattern) //
.withLocale(Locale.getDefault()) //
.withZone(zone) //
::format);
}
private Optional<String> getFileSuffix(Recording rec) {
if (rec.isSingleFile()) {
String filename = rec.getPostProcessedFile().getName();
return Optional.of(filename.substring(filename.lastIndexOf('.') + 1));
} else {
return Optional.empty();
}
}
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()) {
return Optional.of(sanitize(notes.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();
}
}
}

View File

@ -132,4 +132,22 @@ public class AbstractPlaceholderAwarePostProcessorTest extends AbstractPpTest {
String input = "asdf_${modelNotes}_asdf";
assertEquals("asdf_tag,_foo,_bar_asdf", placeHolderAwarePp.fillInPlaceHolders(input, createPostProcessingContext(rec, null, config)));
}
@Test
public void testPlaceholderDefaultValues() throws IOException {
String input = "asdf_${modelGroupName?${modelSanitizedName?anonymous}}_asdf";
PostProcessingContext ctx = createPostProcessingContext(rec, null, config);
ctx.getRecording().getModel().setName(null);
assertEquals("asdf_anonymous_asdf", placeHolderAwarePp.fillInPlaceHolders(input, ctx));
input = "asdf_${modelGroupName?${utcDateTime(yyyy)?anonymous}}_asdf";
assertEquals("asdf_2021_asdf", placeHolderAwarePp.fillInPlaceHolders(input, ctx));
ctx.getRecording().setStartDate(null);
input = "asdf_${modelGroupName?${utcDateTime(yyyy)?anonymous}}_asdf";
assertEquals("asdf_anonymous_asdf", placeHolderAwarePp.fillInPlaceHolders(input, ctx));
input = "asdf_${modelGroupName?${utcDateTime?anonymous}}_asdf";
assertEquals("asdf_anonymous_asdf", placeHolderAwarePp.fillInPlaceHolders(input, ctx));
}
}