package com.notononoto.teamcity.telegram.config; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.JDOMUtil; import com.notononoto.teamcity.telegram.TelegramBotManager; import jetbrains.buildServer.configuration.ChangeListener; import jetbrains.buildServer.configuration.ChangeObserver; import jetbrains.buildServer.configuration.FileWatcher; import jetbrains.buildServer.log.Loggers; import jetbrains.buildServer.serverSide.ServerPaths; import jetbrains.buildServer.serverSide.crypt.EncryptUtil; import jetbrains.buildServer.util.FileUtil; import jetbrains.buildServer.util.StringUtil; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; /** Settings manager */ public class TelegramSettingsManager implements ChangeListener { private static final Logger LOG = Loggers.SERVER; /** Fields names in xml */ private static final String BOT_TOKEN_ATTR = "bot-token"; private static final String PAUSE_ATTR = "paused"; private static final String USE_PROXY_ATTR = "use-proxy"; private static final String PROXY_SERVER_ATTR = "proxy-server"; private static final String PROXY_PORT_ATTR = "proxy-port"; private static final String PROXY_USERNAME_ATTR = "proxy-username"; private static final String PROXY_PASSWORD_ATTR = "proxy-password"; private static final String CONFIG_FILE_NAME = "telegram-config.xml"; /** Configuration file */ private final Path configFile; /** Configuration directory */ private final Path configDir; /** Check file system changes */ private final ChangeObserver changeObserver; /** Telegram bot manager */ private final TelegramBotManager botManager; /** Plugin settings */ private TelegramSettings settings; public TelegramSettingsManager(@NotNull ServerPaths paths, @NotNull TelegramBotManager botManager) throws JDOMException, IOException { configDir = Paths.get(paths.getConfigDir()).resolve("_notifications"). resolve("telegram"); configFile = configDir.resolve(CONFIG_FILE_NAME); this.botManager = botManager; initResources(); reloadConfiguration(); changeObserver = new FileWatcher(configFile.toFile()); changeObserver.setSleepingPeriod(10000L); changeObserver.registerListener(this); changeObserver.start(); } @Override public void changeOccured(String requestor) { try { reloadConfiguration(); } catch (IOException | JDOMException ex) { throw new RuntimeException(ex); } } @NotNull public TelegramSettings getSettings() { return settings; } @NotNull public Path getSettingsDir() { return configDir; } /** * Save configuration on disk * @param newSettings {@link TelegramSettings} */ public synchronized void saveConfiguration(@NotNull TelegramSettings newSettings) { changeObserver.runActionWithDisabledObserver(() -> FileUtil.processXmlFile(configFile.toFile(), (root) -> { root.setAttribute(BOT_TOKEN_ATTR, scramble(newSettings.getBotToken())); root.setAttribute(PAUSE_ATTR, Boolean.toString(newSettings.isPaused())); root.setAttribute(USE_PROXY_ATTR, Boolean.toString(newSettings.isUseProxy())); root.setAttribute(PROXY_SERVER_ATTR, newSettings.getProxyServer()); root.setAttribute(PROXY_PORT_ATTR, storeInteger(newSettings.getProxyPort())); root.setAttribute(PROXY_USERNAME_ATTR, newSettings.getProxyUsername()); root.setAttribute(PROXY_PASSWORD_ATTR, scramble(newSettings.getProxyPassword())); })); settings = newSettings; botManager.reloadIfNeeded(settings); } private synchronized void reloadConfiguration() throws JDOMException, IOException { LOG.info("Loading configuration file: " + configFile); Document document = JDOMUtil.loadDocument(configFile.toFile()); Element root = document.getRootElement(); TelegramSettings newSettings = new TelegramSettings(); newSettings.setBotToken(unscramble(root.getAttributeValue(BOT_TOKEN_ATTR))); newSettings.setPaused(Boolean.parseBoolean(root.getAttributeValue(PAUSE_ATTR))); newSettings.setUseProxy(Boolean.parseBoolean(root.getAttributeValue(USE_PROXY_ATTR))); newSettings.setProxyServer(root.getAttributeValue(PROXY_SERVER_ATTR)); newSettings.setProxyPort(restoreInteger(root.getAttributeValue(PROXY_PORT_ATTR))); newSettings.setProxyUsername(root.getAttributeValue(PROXY_PASSWORD_ATTR)); newSettings.setProxyPassword(unscramble(root.getAttributeValue(PROXY_PASSWORD_ATTR))); settings = newSettings; botManager.reloadIfNeeded(settings); } private void initResources() { try { Files.createDirectories(configDir); copyResourceIfNotExists(configDir, CONFIG_FILE_NAME); copyResourceIfNotExists(configDir, "telegram-config.dtd"); copyResourceIfNotExists(configDir, "build_failed.ftl"); copyResourceIfNotExists(configDir, "build_failed_to_start.ftl"); copyResourceIfNotExists(configDir, "build_failing.ftl"); copyResourceIfNotExists(configDir, "build_probably_hanging.ftl"); copyResourceIfNotExists(configDir, "build_problem_responsibility_assigned_to_me.ftl"); copyResourceIfNotExists(configDir, "build_problem_responsibility_changed.ftl"); copyResourceIfNotExists(configDir, "build_problems_muted.ftl"); copyResourceIfNotExists(configDir, "build_problems_unmuted.ftl"); copyResourceIfNotExists(configDir, "build_started.ftl"); copyResourceIfNotExists(configDir, "build_successful.ftl"); copyResourceIfNotExists(configDir, "build_type_responsibility_assigned_to_me.ftl"); copyResourceIfNotExists(configDir, "build_type_responsibility_changed.ftl"); copyResourceIfNotExists(configDir, "common.ftl"); copyResourceIfNotExists(configDir, "labeling_failed.ftl"); copyResourceIfNotExists(configDir, "multiple_test_responsibility_assigned_to_me.ftl"); copyResourceIfNotExists(configDir, "multiple_test_responsibility_changed.ftl"); copyResourceIfNotExists(configDir, "mute.ftl"); copyResourceIfNotExists(configDir, "responsibility.ftl"); } catch (IOException ex) { LOG.error("Failed to create telegram plugin config directory", ex); } } private void copyResourceIfNotExists(@NotNull Path configDir, @NotNull String name) { FileUtil.copyResourceIfNotExists(this.getClass(), "/telegram_templates/" + name, configDir.resolve(name).toFile()); } private String scramble(String str) { return StringUtil.isEmpty(str) ? str : EncryptUtil.scramble(str); } private String unscramble(String str) { return StringUtil.isEmpty(str) ? str : EncryptUtil.unscramble(str); } private String storeInteger(Integer integer) { // using empty string because null is not valid jdom value return integer == null ? "" : Integer.toString(integer); } private Integer restoreInteger(String str) { return StringUtil.isEmpty(str) ? null : Integer.valueOf(str); } }