package ctbrec; import static java.nio.file.StandardOpenOption.*; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; 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.List; import java.util.Objects; import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.squareup.moshi.JsonAdapter; import com.squareup.moshi.Moshi; import ctbrec.io.ModelJsonAdapter; import ctbrec.sites.Site; public class Config { private static final Logger LOG = LoggerFactory.getLogger(Config.class); private static Config instance; private Settings settings; private String filename; private List sites; private File configDir; public static final String RECORDING_DATE_FORMAT = "yyyy-MM-dd_HH-mm-ss_SSS"; private Config(List 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)) .build(); JsonAdapter adapter = moshi.adapter(Settings.class).lenient(); File configFile = new File(configDir, filename); LOG.debug("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, StandardCharsets.UTF_8.name()).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())); } } 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 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 void save() throws IOException { Moshi moshi = new Moshi.Builder() .add(Model.class, new ModelJsonAdapter()) .build(); JsonAdapter 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; } }