jafea7-ctbrec-v5.3.0-based/common/src/main/java/ctbrec/Config.java

365 lines
14 KiB
Java

package ctbrec;
import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.Moshi;
import ctbrec.io.*;
import ctbrec.recorder.postprocessing.PostProcessor;
import ctbrec.sites.Site;
import ctbrec.sites.chaturbate.ChaturbateModel;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.*;
import java.util.stream.Collectors;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.StandardOpenOption.*;
// TODO don't use singleton pattern
public class Config {
private static final Logger LOG = LoggerFactory.getLogger(Config.class);
private static final String SYSPROP_CONFIG_DIR = "ctbrec.config.dir";
private static final String V_4_7_5 = "4.7.5";
private static Config instance;
private Settings settings;
private final String filename;
private final List<Site> sites;
private final File configDir;
/**
* to temporarily disable saving of the config
* this is useful for the SettingsTab, because setting the initial values of some components causes an immediate save
*/
private boolean savingDisabled = false;
public static final String RECORDING_DATE_FORMAT = "yyyy-MM-dd_HH-mm-ss_SSS";
public Config(List<Site> sites) throws IOException {
this.sites = sites;
copyConfigIfNewVersion();
if (System.getProperty(SYSPROP_CONFIG_DIR) != null) {
configDir = new File(System.getProperty(SYSPROP_CONFIG_DIR), Version.getVersion().toString());
} else {
configDir = new File(OS.getConfigDir(), Version.getVersion().toString());
}
backupConfig(configDir);
if (System.getProperty("ctbrec.config") != null) {
filename = System.getProperty("ctbrec.config");
} else {
filename = "settings.json";
}
}
private void backupConfig(File currentConfigDir) throws IOException {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(RECORDING_DATE_FORMAT);
File src = currentConfigDir;
if (src.exists()) {
File target = new File(src.getParentFile(), src.getName() + "_backup_" + dateTimeFormatter.format(LocalDateTime.now()));
LOG.info("Creating a backup of {} the config in {}", src, target);
FileUtils.copyDirectory(src, target, pathname -> !(pathname.toString().contains("minimal-browser") && pathname.toString().contains("Cache")), true);
deleteOldBackups(currentConfigDir);
}
}
private void deleteOldBackups(File currentConfigDir) {
File parent = currentConfigDir.getParentFile();
File[] backupDirectories = parent.listFiles(file -> file.isDirectory() && file.getName().matches(".*?_backup_\\d{4}-\\d{2}-\\d{2}_\\d{2}-\\d{2}-\\d{2}_\\d{3}"));
Arrays.sort(backupDirectories, Comparator.comparing(File::getName));
for (int i = 0; i < backupDirectories.length - 5; i++) {
File dirToDelete = backupDirectories[i];
try {
LOG.info("Delete old config backup {}", dirToDelete);
IoUtils.deleteDirectory(dirToDelete);
} catch (IOException e) {
LOG.error("Couldn't delete old config backup {}. You might have to delete it manually.", dirToDelete, e);
}
}
}
private void copyConfigIfNewVersion() throws IOException {
File configDirectory;
if (System.getProperty(SYSPROP_CONFIG_DIR) != null) {
configDirectory = new File(System.getProperty(SYSPROP_CONFIG_DIR));
} else {
configDirectory = OS.getConfigDir();
}
Version currentVersion = Version.getVersion();
Version previousVersion = getPreviousVersion(configDirectory);
File src;
File target = new File(configDirectory, currentVersion.toString());
if (target.exists()) {
return;
}
if (previousVersion.compareTo(Version.of(V_4_7_5)) <= 0) {
src = configDirectory;
} else {
src = new File(configDirectory, previousVersion.toString());
}
if (!src.exists()) {
// new installation
return;
}
if (!Objects.equals(previousVersion, currentVersion)) {
LOG.debug("Version update {} -> {}", previousVersion, currentVersion);
LOG.debug("Copying config from {} to {}", src, target);
FileUtils.copyDirectory(src, target, pathname -> !(pathname.toString().contains("minimal-browser") && pathname.toString().contains("Cache")), true);
}
}
private Version getPreviousVersion(File configDirectory) {
File[] versionDirectories = configDirectory.listFiles((dir, name) -> name.matches("\\d+\\.\\d+\\.\\d+"));
if (versionDirectories != null) {
Optional<Version> previousVersion = Arrays.stream(versionDirectories)
.map(File::getName)
.map(Version::of)
.max(Comparator.naturalOrder());
return previousVersion.orElse(Version.of(V_4_7_5));
} else {
return Version.of(V_4_7_5);
}
}
private void load() throws IOException {
Moshi moshi = new Moshi.Builder()
.add(Model.class, new ModelJsonAdapter(sites))
.add(PostProcessor.class, new PostProcessorJsonAdapter())
.add(File.class, new FileJsonAdapter())
.add(UUID.class, new UuidJSonAdapter())
.add(LocalTime.class, new LocalTimeJsonAdapter())
.build();
JsonAdapter<Settings> adapter = moshi.adapter(Settings.class).lenient();
File configFile = new File(configDir, filename);
LOG.info("Loading config from {}", configFile.getAbsolutePath());
if (configFile.exists()) {
try {
byte[] fileContent = Files.readAllBytes(configFile.toPath());
if (fileContent[0] == -17 && fileContent[1] == -69 && fileContent[2] == -65) {
// found BOM (byte order mark)
LOG.debug("Removing BOM from config file");
fileContent[0] = ' ';
fileContent[1] = ' ';
fileContent[2] = ' ';
}
String json = new String(fileContent, UTF_8).trim();
settings = Objects.requireNonNull(adapter.fromJson(json));
settings.httpTimeout = Math.max(settings.httpTimeout, 10_000);
if (settings.recordingsDir.endsWith("/")) {
settings.recordingsDir = settings.recordingsDir.substring(0, settings.recordingsDir.length() - 1);
}
} catch (Exception e) {
settings = OS.getDefaultSettings();
for (Site site : sites) {
site.setEnabled(!settings.disabledSites.contains(site.getName()));
}
throw e;
}
} else {
LOG.error("Config file does not exist. Falling back to default values.");
settings = OS.getDefaultSettings();
}
for (Site site : sites) {
site.setEnabled(!settings.disabledSites.contains(site.getName()));
}
migrateOldSettings();
}
private void migrateOldSettings() {
// 5.0.0
convertChaturbateModelNamesToLowerCase();
}
private void convertChaturbateModelNamesToLowerCase() {
final String CTB = "chaturbate.com";
// convert model notes
Map<String, String> convertedModelNotes = new HashMap<>();
getSettings().modelNotes.forEach((key, value) -> {
if (key.contains(CTB)) {
convertedModelNotes.put(key.toLowerCase(), value);
} else {
convertedModelNotes.put(key, value);
}
});
getSettings().modelNotes.clear();
getSettings().modelNotes.putAll(convertedModelNotes);
// convert model portraits
Map<String, String> convertedModelPortraits = new HashMap<>();
getSettings().modelPortraits.forEach((key, value) -> {
if (key.contains(CTB)) {
convertedModelPortraits.put(key.toLowerCase(), value);
} else {
convertedModelPortraits.put(key, value);
}
});
getSettings().modelPortraits.clear();
getSettings().modelPortraits.putAll(convertedModelPortraits);
// convert model groups
getSettings().modelGroups.forEach(mg -> mg.setModelUrls(mg.getModelUrls().stream()
.filter(Objects::nonNull)
.map(url -> url.contains(CTB) ? url.toLowerCase() : url)
.collect(Collectors.toList()))); // NOSONAR - has to be mutable
// convert ignored models
getSettings().ignoredModels = getSettings().ignoredModels.stream()
.filter(Objects::nonNull)
.map(url -> url.contains(CTB) ? url.toLowerCase() : url)
.collect(Collectors.toList()); // NOSONAR - has to be mutable
// change the model objects
getSettings().models.stream()
.filter(ChaturbateModel.class::isInstance)
.filter(m -> m.getUrl() != null)
.forEach(m -> {
m.setDisplayName(m.getName());
m.setName(m.getName().toLowerCase());
m.setUrl(m.getUrl().toLowerCase());
});
getSettings().recordLater.stream()
.filter(ChaturbateModel.class::isInstance)
.filter(m -> m.getUrl() != null)
.forEach(m -> {
m.setDisplayName(m.getName());
m.setName(m.getName().toLowerCase());
m.setUrl(m.getUrl().toLowerCase());
});
}
public static synchronized void init(List<Site> sites) throws IOException {
if (instance == null) {
instance = new Config(sites);
instance.load();
}
}
public static synchronized Config getInstance() {
if (instance == null) {
throw new IllegalStateException("Config not initialized, call init() first");
}
return instance;
}
public Settings getSettings() {
return settings;
}
public synchronized void save() throws IOException {
if (savingDisabled) {
return;
}
Moshi moshi = new Moshi.Builder()
.add(Model.class, new ModelJsonAdapter())
.add(PostProcessor.class, new PostProcessorJsonAdapter())
.add(File.class, new FileJsonAdapter())
.add(UUID.class, new UuidJSonAdapter())
.add(LocalTime.class, new LocalTimeJsonAdapter())
.build();
JsonAdapter<Settings> adapter = moshi.adapter(Settings.class).indent(" ");
String json = adapter.toJson(settings);
File configFile = new File(configDir, filename);
LOG.debug("Saving config to {}", configFile.getAbsolutePath());
Files.createDirectories(configDir.toPath());
Files.writeString(configFile.toPath(), json, CREATE, WRITE, TRUNCATE_EXISTING);
}
public static boolean isServerMode() {
return Objects.equals(System.getProperty("ctbrec.server.mode"), "1");
}
public static boolean isDevMode() {
return Objects.equals(System.getenv("CTBREC_DEV"), "1");
}
public File getConfigDir() {
return configDir;
}
public File getFileForRecording(Model model, String suffix, Instant startTime) {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(RECORDING_DATE_FORMAT);
LocalDateTime startDateTime = LocalDateTime.ofInstant(startTime, ZoneId.systemDefault());
String formattedDate = dateTimeFormatter.format(startDateTime);
File dirForRecording = getDirForRecording(model, formattedDate);
File targetFile = new File(dirForRecording, model.getSanitizedNamed() + '_' + formattedDate + '.' + suffix);
return targetFile;
}
private File getDirForRecording(Model model, String formattedDate) {
switch (getSettings().recordingsDirStructure) {
case ONE_PER_MODEL -> {
return new File(getSettings().recordingsDir, model.getSanitizedNamed());
}
case ONE_PER_RECORDING -> {
File modelDir = new File(getSettings().recordingsDir, model.getSanitizedNamed());
return new File(modelDir, formattedDate);
}
default -> {
return new File(getSettings().recordingsDir);
}
}
}
public String getServerUrl() {
String scheme = getSettings().transportLayerSecurity ? "https" : "http";
int port = getSettings().httpPort;
String baseUrl = scheme + "://" + getSettings().httpServer + ":" + port + getContextPath();
return baseUrl;
}
public String getContextPath() {
String context = Optional.ofNullable(getSettings().servletContext).orElse("");
if (!context.startsWith("/") && !context.isEmpty()) {
context = '/' + context;
}
if (context.endsWith("/")) {
context = context.substring(0, context.length() - 1);
}
return context;
}
public DateTimeFormatter getDateTimeFormatter() {
var dtf = getSettings().dateTimeFormat;
if (StringUtil.isNotBlank(dtf)) {
return DateTimeFormatter.ofPattern(dtf);
} else {
return DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);
}
}
// public String getModelNotes(Model m) {
// return Config.getInstance().getSettings().modelNotes.getOrDefault(m.getUrl(), "");
// }
public void disableSaving() {
savingDisabled = true;
}
public void enableSaving() {
savingDisabled = false;
}
public boolean isIgnored(Model model) {
List<String> ignored = Config.getInstance().getSettings().ignoredModels;
return ignored.contains(model.getUrl());
}
}