forked from j62/ctbrec
315 lines
12 KiB
Java
315 lines
12 KiB
Java
package ctbrec;
|
|
|
|
import static java.nio.charset.StandardCharsets.*;
|
|
import static java.nio.file.StandardOpenOption.*;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.StandardCopyOption;
|
|
import java.text.SimpleDateFormat;
|
|
import java.time.Instant;
|
|
import java.time.LocalDateTime;
|
|
import java.time.ZoneId;
|
|
import java.time.format.DateTimeFormatter;
|
|
import java.util.Date;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Map.Entry;
|
|
import java.util.Objects;
|
|
import java.util.Optional;
|
|
import java.util.UUID;
|
|
import java.util.stream.Collectors;
|
|
|
|
import org.apache.commons.io.FileUtils;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import com.squareup.moshi.JsonAdapter;
|
|
import com.squareup.moshi.Moshi;
|
|
|
|
import ctbrec.Settings.SplitStrategy;
|
|
import ctbrec.io.FileJsonAdapter;
|
|
import ctbrec.io.ModelJsonAdapter;
|
|
import ctbrec.io.PostProcessorJsonAdapter;
|
|
import ctbrec.io.UuidJSonAdapter;
|
|
import ctbrec.recorder.postprocessing.DeleteTooShort;
|
|
import ctbrec.recorder.postprocessing.PostProcessor;
|
|
import ctbrec.recorder.postprocessing.RemoveKeepFile;
|
|
import ctbrec.recorder.postprocessing.Script;
|
|
import ctbrec.sites.Site;
|
|
import ctbrec.sites.cam4.Cam4Model;
|
|
|
|
public class Config {
|
|
|
|
private static final Logger LOG = LoggerFactory.getLogger(Config.class);
|
|
|
|
private static Config instance;
|
|
private Settings settings;
|
|
private String filename;
|
|
private List<Site> sites;
|
|
private 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";
|
|
|
|
private Config(List<Site> sites) {
|
|
this.sites = sites;
|
|
if(System.getProperty("ctbrec.config.dir") != null) {
|
|
configDir = new File(System.getProperty("ctbrec.config.dir"));
|
|
} else {
|
|
configDir = OS.getConfigDir();
|
|
}
|
|
|
|
if(System.getProperty("ctbrec.config") != null) {
|
|
filename = System.getProperty("ctbrec.config");
|
|
} else {
|
|
filename = "settings.json";
|
|
}
|
|
}
|
|
|
|
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())
|
|
.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 = 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()));
|
|
}
|
|
makeBackup(configFile);
|
|
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();
|
|
}
|
|
|
|
@SuppressWarnings("deprecation")
|
|
private void migrateOldSettings() {
|
|
// 3.8.0 from maxResolution only to resolution range
|
|
if(settings.minimumResolution == settings.maximumResolution && settings.minimumResolution == 0) {
|
|
settings.minimumResolution = 0;
|
|
settings.maximumResolution = 8640;
|
|
}
|
|
|
|
// 3.10.0
|
|
if (StringUtil.isNotBlank(settings.postProcessing)) {
|
|
Script script = new Script();
|
|
script.getConfig().put(Script.SCRIPT_EXECUTABLE, settings.postProcessing);
|
|
script.getConfig().put(Script.SCRIPT_PARAMS, "${absoluteParentPath} ${absolutePath} ${modelName} ${siteName} ${epochSecond}");
|
|
settings.postProcessors.add(script);
|
|
settings.postProcessing = null;
|
|
}
|
|
if (settings.minimumLengthInSeconds > 0) {
|
|
DeleteTooShort deleteTooShort = new DeleteTooShort();
|
|
deleteTooShort.getConfig().put(DeleteTooShort.MIN_LEN_IN_SECS, Integer.toString(settings.minimumLengthInSeconds));
|
|
settings.postProcessors.add(deleteTooShort);
|
|
settings.minimumLengthInSeconds = 0;
|
|
}
|
|
if (settings.removeRecordingAfterPostProcessing) {
|
|
RemoveKeepFile removeKeepFile = new RemoveKeepFile();
|
|
settings.postProcessors.add(removeKeepFile);
|
|
settings.removeRecordingAfterPostProcessing = false;
|
|
}
|
|
|
|
// 3.10.7
|
|
if (StringUtil.isNotBlank(settings.username)) {
|
|
settings.chaturbateUsername = settings.username;
|
|
settings.username = null;
|
|
}
|
|
if (StringUtil.isNotBlank(settings.password)) {
|
|
settings.chaturbatePassword = settings.password;
|
|
settings.password = null;
|
|
}
|
|
if (settings.splitRecordings > 0) {
|
|
settings.splitStrategy = SplitStrategy.TIME;
|
|
settings.splitRecordingsAfterSecs = settings.splitRecordings;
|
|
settings.splitRecordings = 0;
|
|
}
|
|
// migrate old config from ctbrec-minimal browser
|
|
File oldLocation = new File(OS.getConfigDir().getParentFile(), "ctbrec-minimal-browser");
|
|
if (oldLocation.exists()) {
|
|
File newLocation = new File(getConfigDir(), oldLocation.getName());
|
|
try {
|
|
if (!newLocation.exists()) {
|
|
LOG.debug("Moving minimal browser config {} --> {}", oldLocation, newLocation);
|
|
FileUtils.moveDirectory(oldLocation, newLocation);
|
|
} else {
|
|
LOG.debug("minimal browser settings have been migrated before");
|
|
}
|
|
} catch (IOException e) {
|
|
LOG.error("Couldn't migrate minimal browser config location", e);
|
|
}
|
|
}
|
|
// 3.10.10 model notes due to Cam4 URL change
|
|
for (Iterator<Entry<String, String>> iterator = settings.modelNotes.entrySet().iterator(); iterator.hasNext();) {
|
|
Entry<String, String> note = iterator.next();
|
|
if (note.getKey().contains("cam4") && note.getKey().endsWith("/")) {
|
|
Cam4Model model = new Cam4Model();
|
|
model.setUrl(note.getKey());
|
|
settings.modelNotes.put(model.getUrl(), note.getValue());
|
|
iterator.remove();
|
|
}
|
|
}
|
|
// 3.11.0 make Cam4 model names lower case
|
|
settings.models.stream()
|
|
.filter(Cam4Model.class::isInstance)
|
|
.forEach(m -> m.setName(m.getName().toLowerCase()));
|
|
settings.modelsIgnored.stream()
|
|
.filter(Cam4Model.class::isInstance)
|
|
.forEach(m -> m.setName(m.getName().toLowerCase()));
|
|
// 4.1.2 reduce models ignore to store only the URL
|
|
if (settings.modelsIgnored != null && !settings.modelsIgnored.isEmpty()) {
|
|
settings.ignoredModels = settings.modelsIgnored.stream()
|
|
.map(Model::getUrl)
|
|
.collect(Collectors.toList());
|
|
settings.modelsIgnored = null;
|
|
}
|
|
}
|
|
|
|
private void makeBackup(File source) {
|
|
try {
|
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
|
|
String timestamp = sdf.format(new Date());
|
|
String backup = source.getName() + '.' + timestamp;
|
|
File target = new File(source.getParentFile(), backup);
|
|
Files.copy(source.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
|
} catch (Exception e) {
|
|
LOG.error("Couldn't create backup of settings file", e);
|
|
}
|
|
}
|
|
|
|
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())
|
|
.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.write(configFile.toPath(), json.getBytes(UTF_8), 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);
|
|
case FLAT:
|
|
default:
|
|
return new File(getSettings().recordingsDir);
|
|
}
|
|
}
|
|
|
|
public String getServerUrl() {
|
|
String scheme = getSettings().transportLayerSecurity ? "https" : "http";
|
|
// int port = getSettings().transportLayerSecurity ? getSettings().httpSecurePort : getSettings().httpPort;
|
|
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 String getModelNotes(Model m) {
|
|
return Config.getInstance().getSettings().modelNotes.getOrDefault(m.getUrl(), "");
|
|
}
|
|
|
|
public void disableSaving() {
|
|
savingDisabled = true;
|
|
}
|
|
|
|
public void enableSaving() {
|
|
savingDisabled = false;
|
|
}
|
|
}
|