package com.jetbrains.help.context; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.http.HttpUtil; import cn.hutool.json.JSONUtil; import com.jetbrains.help.util.FileTools; import lombok.AccessLevel; import lombok.Data; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.concurrent.CompletableFuture; @Slf4j @NoArgsConstructor(access = AccessLevel.PRIVATE) public class PluginsContextHolder { private static final String PLUGIN_BASIC_URL = "https://plugins.jetbrains.com"; private static final String PLUGIN_LIST_URL = PLUGIN_BASIC_URL + "/api/searchPlugins?max=10000&offset=0&orderBy=name"; private static final String PLUGIN_INFO_URL = PLUGIN_BASIC_URL + "/api/plugins/"; private static final String PLUGIN_JSON_FILE_NAME = "external/data/plugin.json"; private static List pluginCacheList; private static File pluginsJsonFile; public static void init() { log.info("Plugin context init loading..."); pluginsJsonFile = FileTools.getFileOrCreat(PLUGIN_JSON_FILE_NAME); String pluginJsonArray; try { pluginJsonArray = IoUtil.readUtf8(FileUtil.getInputStream(pluginsJsonFile)); } catch (IORuntimeException e) { throw new IllegalArgumentException(CharSequenceUtil.format("{} File read failed", PLUGIN_JSON_FILE_NAME), e); } if (CharSequenceUtil.isBlank(pluginJsonArray) || !JSONUtil.isTypeJSON(pluginJsonArray)) { refreshJsonFile(); } else { pluginCacheList = JSONUtil.toList(pluginJsonArray, PluginCache.class); log.info("Plugin context init success !"); } } public static List pluginCacheList() { return PluginsContextHolder.pluginCacheList; } public static void refreshJsonFile() { log.info("Init or Refresh plugin context from 'JetBrains.com' loading..."); CompletableFuture .supplyAsync(PluginsContextHolder::pluginList) .thenApply(PluginsContextHolder::pluginListFilter) .thenApply(PluginsContextHolder::pluginConversion) .thenAccept(PluginsContextHolder::overrideJsonFile) .exceptionally(throwable -> { log.error("Plugin context init or refresh failed", throwable); return null; }); log.info("Init or Refresh plugin context success !"); } public static void overrideJsonFile(List pluginCaches) { pluginCacheList.addAll(pluginCaches); String jsonStr = JSONUtil.toJsonStr(pluginCacheList); try { FileUtil.writeString(jsonStr, pluginsJsonFile, StandardCharsets.UTF_8); } catch (IORuntimeException e) { throw new IllegalArgumentException(CharSequenceUtil.format("{} File write failed", PLUGIN_JSON_FILE_NAME), e); } } public static PluginList pluginList() { return HttpUtil.createGet(PLUGIN_LIST_URL) .thenFunction(response -> { try (InputStream is = response.bodyStream()) { if (!response.isOk()) { throw new IllegalArgumentException(CharSequenceUtil.format("{} The request failed = {}", PLUGIN_LIST_URL, response)); } return IoUtil.readObj(is, PluginList.class); } catch (IOException e) { throw new IllegalArgumentException(CharSequenceUtil.format("{} The request io read failed", PLUGIN_LIST_URL), e); } }); } public static List pluginListFilter(PluginList pluginList) { return pluginList.getPlugins() .stream() .filter(plugin -> !PluginsContextHolder.pluginCacheList.contains(new PluginCache().setId(plugin.getId()))) .filter(plugin -> !CharSequenceUtil.equals(plugin.getPricingModel(), "FREE")) .toList(); } public static List pluginConversion(List pluginList) { return pluginList .stream() .map(plugin -> { String productCode = pluginInfo(plugin.getId()).getPurchaseInfo().getProductCode(); return new PluginCache() .setId(plugin.getId()) .setProductCode(productCode) .setName(plugin.getName()) .setPricingModel(plugin.getPricingModel()) .setIcon(PLUGIN_BASIC_URL + plugin.getIcon()) ; }) .toList(); } public static PluginInfo pluginInfo(Long pluginId) { return HttpUtil.createGet(PLUGIN_INFO_URL + pluginId) .thenFunction(response -> { try (InputStream is = response.bodyStream()) { if (!response.isOk()) { throw new IllegalArgumentException(CharSequenceUtil.format("{} The request failed = {}", PLUGIN_INFO_URL, response)); } return IoUtil.readObj(is, PluginInfo.class); } catch (IOException e) { throw new IllegalArgumentException(CharSequenceUtil.format("{} The request io read failed", PLUGIN_LIST_URL), e); } }); } @Data public static class PluginCache { private Long id; private String productCode; private String name; private String pricingModel; private String icon; @Override public final boolean equals(Object o) { if (this == o) return true; if (!(o instanceof PluginCache that)) return false; return id.equals(that.id); } @Override public int hashCode() { return id.hashCode(); } } @Data public static class PluginInfo { private Long id; private PurchaseInfo purchaseInfo; @Data public static class PurchaseInfo { private String productCode; } } @Data public static class PluginList { private List plugins; private Long total; @Data public static class Plugin { private Long id; private String name; private String preview; private Integer downloads; private String pricingModel; private String organization; private String icon; private String previewImage; private Double rating; private VendorInfo vendorInfo; } @Data public static class VendorInfo { private String name; private Boolean isVerified; } } }