diff --git a/app/src/main/java/ru/kirillius/pf/sdn/App.java b/app/src/main/java/ru/kirillius/pf/sdn/App.java index 412d2ad..1d6b1c5 100644 --- a/app/src/main/java/ru/kirillius/pf/sdn/App.java +++ b/app/src/main/java/ru/kirillius/pf/sdn/App.java @@ -59,7 +59,7 @@ public class App implements Context, Closeable { } catch (IOException e) { loadedConfig = new Config(); loadedConfig.setSubscriptions(new ArrayList<>(List.of( - new RepositoryConfig("updates", GitSubscription.class, "https://git.kirillius.ru/kirillius/protected-resources-list.git") + new RepositoryConfig("updates", GitSubscription.class, "https://git.kirillius.ru/kirillius/protected-resources-list.git", "") ))); try { Config.store(loadedConfig, launcherConfig.getConfigFile()); diff --git a/app/src/main/java/ru/kirillius/pf/sdn/External/API/GitSubscription.java b/app/src/main/java/ru/kirillius/pf/sdn/External/API/GitSubscription.java index 020a006..52fa579 100644 --- a/app/src/main/java/ru/kirillius/pf/sdn/External/API/GitSubscription.java +++ b/app/src/main/java/ru/kirillius/pf/sdn/External/API/GitSubscription.java @@ -3,9 +3,6 @@ package ru.kirillius.pf.sdn.External.API; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; -import org.json.JSONObject; -import org.json.JSONTokener; -import ru.kirillius.json.JSONUtility; import ru.kirillius.pf.sdn.core.Context; import ru.kirillius.pf.sdn.core.Networking.NetworkResourceBundle; import ru.kirillius.pf.sdn.core.Subscription.RepositoryConfig; @@ -14,14 +11,16 @@ import ru.kirillius.pf.sdn.core.Util.HashUtil; import ru.kirillius.utils.logging.SystemLogger; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.util.*; +import java.util.Collections; +import java.util.Map; +import java.util.StringJoiner; /** * Subscription provider that pulls JSON resource bundles from a Git repository cache. */ public class GitSubscription implements SubscriptionProvider { + private final static String CTX = GitSubscription.class.getSimpleName(); private final Context context; /** @@ -31,90 +30,43 @@ public class GitSubscription implements SubscriptionProvider { this.context = context; } - /** - * Clones or updates the configured repository and loads resource bundles from the {@code resources} directory. - */ - @Override - public Map getResources(RepositoryConfig config) { - try { - var repoDir = new File(context.getConfig().getCacheDirectory(), "git/" + HashUtil.md5(config.getName())); - - if (!repoDir.exists()) { - if (!repoDir.mkdirs()) { - throw new IOException("Unable to create directory: " + repoDir.getAbsolutePath()); - } - } - - var existing = isGitRepository(repoDir); - var repository = existing ? openRepository(repoDir) : cloneRepository(config.getSource(), repoDir); - if (existing) { - SystemLogger.message("Fetching git repository " + config.getName() + " (" + config.getSource() + ")", CTX); - checkAndPullUpdates(repository); - } - repository.close(); - - var resourcesDir = new File(repoDir, "resources"); - if (!resourcesDir.exists()) { - SystemLogger.error(resourcesDir + " is not exist in repo (" + config.getSource() + ")", CTX); - return Collections.emptyMap(); - } - - var map = new HashMap(); - for (var file : Objects.requireNonNull(resourcesDir.listFiles())) { - try (var stream = new FileInputStream(file)) { - var name = file.getName(); - if (!name.endsWith(".json")) { - continue; - } - var bundle = JSONUtility.deserializeStructure(new JSONObject(new JSONTokener(stream)), NetworkResourceBundle.class); - map.put(name.substring(0, name.length() - 5), bundle); - } - } - - return map; - } catch (Exception e) { - SystemLogger.error("Error while reading git repository", CTX, e); - throw new RuntimeException(e); - } - } - - /** * Runs {@code git fetch} and {@code git pull} when remote updates are available. */ - private static void checkAndPullUpdates(Git git) throws GitAPIException { + private static void checkAndPullUpdates(Git git) { + try { + var fetchResult = git.fetch() + .setCheckFetchedObjects(true) + .call(); - var fetchResult = git.fetch() - .setCheckFetchedObjects(true) - .call(); + if (fetchResult.getTrackingRefUpdates() != null && !fetchResult.getTrackingRefUpdates().isEmpty()) { + SystemLogger.message("Downloading updates...", CTX); + var pullResult = git.pull().call(); - if (fetchResult.getTrackingRefUpdates() != null && !fetchResult.getTrackingRefUpdates().isEmpty()) { - SystemLogger.message("Downloading updates...", CTX); - var pullResult = git.pull().call(); + if (pullResult.isSuccessful()) { + SystemLogger.message("Git pull is successful", CTX); - if (pullResult.isSuccessful()) { - SystemLogger.message("Git pull is successful", CTX); - - // Проверяем были ли обновлены файлы - if (pullResult.getFetchResult() != null && !pullResult.getFetchResult().getTrackingRefUpdates().isEmpty()) { - var updatedFiles = new StringJoiner("\n"); - pullResult.getFetchResult().getTrackingRefUpdates().forEach(refUpdate -> updatedFiles.add(" - " + refUpdate.getLocalName() + - " : " + refUpdate.getOldObjectId().abbreviate(7).name() + - " -> " + refUpdate.getNewObjectId().abbreviate(7).name())); - SystemLogger.message("Updated files: " + updatedFiles, CTX); + // Проверяем были ли обновлены файлы + if (pullResult.getFetchResult() != null && !pullResult.getFetchResult().getTrackingRefUpdates().isEmpty()) { + var updatedFiles = new StringJoiner("\n"); + pullResult.getFetchResult().getTrackingRefUpdates().forEach(refUpdate -> updatedFiles.add(" - " + refUpdate.getLocalName() + + " : " + refUpdate.getOldObjectId().abbreviate(7).name() + + " -> " + refUpdate.getNewObjectId().abbreviate(7).name())); + SystemLogger.message("Updated files: " + updatedFiles, CTX); + } + } else { + SystemLogger.error("Download failed", CTX); } } else { - SystemLogger.error("Download failed", CTX); + SystemLogger.message("There is no updates", CTX); } - } else { - SystemLogger.message("There is no updates", CTX); + } catch (Exception e) { + SystemLogger.error("Failed to fetch & pull repository", CTX, e); } } - private final static String CTX = GitSubscription.class.getSimpleName(); - /** * Clones the repository into the given path. */ @@ -150,4 +102,49 @@ public class GitSubscription implements SubscriptionProvider { var gitDir = new File(directory, ".git"); return gitDir.exists() && gitDir.isDirectory(); } + + /** + * Clones or updates the configured repository and loads resource bundles from the {@code resources} directory. + */ + @Override + public Map getResources(RepositoryConfig config) { + try { + var repoDir = new File(context.getConfig().getCacheDirectory(), "git/" + HashUtil.md5(config.getName())); + + if (!repoDir.exists()) { + if (!repoDir.mkdirs()) { + throw new IOException("Unable to create directory: " + repoDir.getAbsolutePath()); + } + } + + var existing = isGitRepository(repoDir); + var repository = existing ? openRepository(repoDir) : cloneRepository(config.getSource(), repoDir); + if (existing) { + SystemLogger.message("Fetching git repository " + config.getName() + " (" + config.getSource() + ")", CTX); + checkAndPullUpdates(repository); + } + repository.close(); + + if(!config.getScript().isBlank()){ + try (var shell = new ShellExecutor(ShellExecutor.Config.builder().useSSH(false).build())) { + var result = shell.executeCommand(new String[]{ + config.getScript(), + config.getSource() + }); + SystemLogger.message("Shell command execution result:"+result, CTX); + } + } + + var resourcesDir = new File(repoDir, "resources"); + if (!resourcesDir.exists()) { + SystemLogger.error(resourcesDir + " is not exist in repo (" + config.getSource() + ")", CTX); + return Collections.emptyMap(); + } + + return LocalFilesystemSubscription.scanLocalDirectory(resourcesDir); + } catch (Exception e) { + SystemLogger.error("Error while reading git repository", CTX, e); + throw new RuntimeException(e); + } + } } diff --git a/app/src/main/java/ru/kirillius/pf/sdn/External/API/LocalFilesystemSubscription.java b/app/src/main/java/ru/kirillius/pf/sdn/External/API/LocalFilesystemSubscription.java new file mode 100644 index 0000000..1e3ee09 --- /dev/null +++ b/app/src/main/java/ru/kirillius/pf/sdn/External/API/LocalFilesystemSubscription.java @@ -0,0 +1,77 @@ +package ru.kirillius.pf.sdn.External.API; + +import org.json.JSONObject; +import org.json.JSONTokener; +import ru.kirillius.json.JSONUtility; +import ru.kirillius.pf.sdn.core.Context; +import ru.kirillius.pf.sdn.core.Networking.NetworkResourceBundle; +import ru.kirillius.pf.sdn.core.Subscription.RepositoryConfig; +import ru.kirillius.pf.sdn.core.Subscription.SubscriptionProvider; +import ru.kirillius.utils.logging.SystemLogger; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Subscription provider that pulls JSON resource bundles from a Git repository cache. + */ +public class LocalFilesystemSubscription implements SubscriptionProvider { + private final static String CTX = LocalFilesystemSubscription.class.getSimpleName(); + private final Context context; + + /** + * Creates the provider using the shared application context. + */ + public LocalFilesystemSubscription(Context context) { + this.context = context; + } + + /** + * Clones or updates the configured repository and loads resource bundles from the {@code resources} directory. + */ + @Override + public Map getResources(RepositoryConfig config) { + try { + var resourcesDir = new File(config.getSource()); + if (!resourcesDir.exists()) { + SystemLogger.error(resourcesDir + " is not exist directory", CTX); + return Collections.emptyMap(); + } + if(!config.getScript().isBlank()){ + try (var shell = new ShellExecutor(ShellExecutor.Config.builder().useSSH(false).build())) { + var result = shell.executeCommand(new String[]{ + config.getScript(), + config.getSource() + }); + SystemLogger.message("Shell command execution result:"+result, CTX); + } + } + return scanLocalDirectory(resourcesDir); + + } catch (Exception e) { + SystemLogger.error("Error while reading local FS repository", CTX, e); + throw new RuntimeException(e); + } + } + + public static Map scanLocalDirectory(File resourcesDir) throws IOException { + var map = new HashMap(); + for (var file : Objects.requireNonNull(resourcesDir.listFiles())) { + try (var stream = new FileInputStream(file)) { + var name = file.getName(); + if (!name.endsWith(".json")) { + continue; + } + var bundle = JSONUtility.deserializeStructure(new JSONObject(new JSONTokener(stream)), NetworkResourceBundle.class); + map.put(name.substring(0, name.length() - 5), bundle); + } + } + + return map; + } +} diff --git a/app/src/main/java/ru/kirillius/pf/sdn/External/API/TDNSAPI.java b/app/src/main/java/ru/kirillius/pf/sdn/External/API/TDNSAPI.java index 71eee72..ce7b530 100644 --- a/app/src/main/java/ru/kirillius/pf/sdn/External/API/TDNSAPI.java +++ b/app/src/main/java/ru/kirillius/pf/sdn/External/API/TDNSAPI.java @@ -14,6 +14,7 @@ import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.util.*; /** @@ -29,7 +30,7 @@ public class TDNSAPI implements Closeable { */ public TDNSAPI(String server, String authToken) { this.server = server; - httpClient = HttpClient.newBuilder().build(); + httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(30)).build(); this.authToken = authToken; } diff --git a/app/src/main/java/ru/kirillius/pf/sdn/web/RPC/System.java b/app/src/main/java/ru/kirillius/pf/sdn/web/RPC/System.java index 7c82cc5..b7ff545 100644 --- a/app/src/main/java/ru/kirillius/pf/sdn/web/RPC/System.java +++ b/app/src/main/java/ru/kirillius/pf/sdn/web/RPC/System.java @@ -6,6 +6,7 @@ import ru.kirillius.json.JSONUtility; import ru.kirillius.json.rpc.Annotations.JRPCArgument; import ru.kirillius.json.rpc.Annotations.JRPCMethod; import ru.kirillius.pf.sdn.External.API.GitSubscription; +import ru.kirillius.pf.sdn.External.API.LocalFilesystemSubscription; import ru.kirillius.pf.sdn.InMemoryLogHandler; import ru.kirillius.pf.sdn.core.*; import ru.kirillius.pf.sdn.web.ProtectedMethod; @@ -124,6 +125,8 @@ public class System implements RPC { public JSONArray getRepositoryTypes() { var array = new JSONArray(); array.put(GitSubscription.class.getName()); + array.put(LocalFilesystemSubscription.class.getName()); + return array; } diff --git a/core/src/main/java/ru/kirillius/pf/sdn/core/Subscription/RepositoryConfig.java b/core/src/main/java/ru/kirillius/pf/sdn/core/Subscription/RepositoryConfig.java index dfc06c6..4e8f3ee 100644 --- a/core/src/main/java/ru/kirillius/pf/sdn/core/Subscription/RepositoryConfig.java +++ b/core/src/main/java/ru/kirillius/pf/sdn/core/Subscription/RepositoryConfig.java @@ -28,4 +28,9 @@ public class RepositoryConfig { @Getter @JSONProperty private String source; + + @Setter + @Getter + @JSONProperty(required = false) + private String script = ""; } diff --git a/webui/src/pages/Settings.js b/webui/src/pages/Settings.js index eaf2358..741e505 100644 --- a/webui/src/pages/Settings.js +++ b/webui/src/pages/Settings.js @@ -30,7 +30,8 @@ const CLASS_NAMES = { subscriptionRemove: 'settings-subscription-remove', subscriptionName: 'settings-subscription-name', subscriptionType: 'settings-subscription-type', - subscriptionSource: 'settings-subscription-source' + subscriptionSource: 'settings-subscription-source', + subscriptionScript: 'settings-subscription-script' }; let currentConfig = {}; @@ -151,6 +152,7 @@ function createSubscriptionRow(subscription = {}, index = 0) { const name = subscription.name || ''; const type = subscription.type || ''; const source = subscription.source || ''; + const script = subscription.script || ''; const uid = `${Date.now()}-${index}`; return ` @@ -173,6 +175,10 @@ function createSubscriptionRow(subscription = {}, index = 0) { +
+ + +
`; } @@ -342,14 +348,15 @@ function collectSubscriptions() { const name = $entry.find(`.${CLASS_NAMES.subscriptionName}`).val().trim(); const type = $entry.find(`.${CLASS_NAMES.subscriptionType}`).val(); const source = $entry.find(`.${CLASS_NAMES.subscriptionSource}`).val().trim(); + const script = $entry.find(`.${CLASS_NAMES.subscriptionScript}`).val().trim(); if (!type) { hasEmptyType = true; return false; } - if (name || type || source) { - subscriptions.push({ name, type, source }); + if (name || type || source || script) { + subscriptions.push({ name, type, source, script }); } return true; });