package com.claudecode.plugin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.*; import java.util.*; import java.util.concurrent.*; /** * 插件自动更新器 —— 每日检查已安装插件的更新。 *

* 对应 claude-code 中插件自动更新逻辑。 *

* 特性: *

*/ public class PluginAutoUpdate { private static final Logger log = LoggerFactory.getLogger(PluginAutoUpdate.class); private final PluginInstaller installer; private final PluginManager pluginManager; private final String marketplaceUrl; /** 是否自动安装更新 */ private boolean autoInstall = false; /** 检查间隔 */ private Duration checkInterval = Duration.ofHours(24); /** 上次检查时间 */ private Instant lastCheckTime = Instant.EPOCH; /** 可用更新缓存 */ private final ConcurrentHashMap pendingUpdates = new ConcurrentHashMap<>(); /** 更新通知回调 */ private UpdateNotificationCallback notificationCallback; private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(r -> { Thread t = new Thread(r, "plugin-auto-update"); t.setDaemon(true); return t; }); public PluginAutoUpdate(PluginInstaller installer, PluginManager pluginManager) { this(installer, pluginManager, "https://marketplace.claude-code.dev/api/v1"); } public PluginAutoUpdate(PluginInstaller installer, PluginManager pluginManager, String marketplaceUrl) { this.installer = installer; this.pluginManager = pluginManager; this.marketplaceUrl = marketplaceUrl; } /** * 启动定期检查。 */ public void start() { long intervalHours = checkInterval.toHours(); scheduler.scheduleWithFixedDelay(this::checkForUpdates, 1, intervalHours, TimeUnit.HOURS); log.info("Plugin auto-update started (interval: {}h)", intervalHours); } /** * 停止定期检查。 */ public void stop() { scheduler.shutdown(); try { if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) { scheduler.shutdownNow(); } } catch (InterruptedException e) { scheduler.shutdownNow(); Thread.currentThread().interrupt(); } } /** * 立即检查所有已安装插件的更新。 */ public Map checkForUpdates() { Map installed = installer.listInstalled(); if (installed.isEmpty()) { log.debug("No installed plugins to check for updates"); return Map.of(); } log.info("Checking updates for {} plugins...", installed.size()); // 并行检查所有插件 List> futures = new ArrayList<>(); for (var entry : installed.values()) { futures.add(installer.checkUpdate( entry.id(), entry.version(), marketplaceUrl)); } // 等待所有结果 Map results = new HashMap<>(); for (var future : futures) { try { var result = future.get(30, TimeUnit.SECONDS); if (result.hasUpdate()) { results.put(result.pluginId(), result); pendingUpdates.put(result.pluginId(), result); log.info("Update available: {} {} → {}", result.pluginId(), result.currentVersion(), result.latestVersion()); // 自动安装 if (autoInstall && result.downloadUrl() != null) { var installed2 = installer.listInstalled().get(result.pluginId()); if (installed2 != null) { var installResult = installer.install( result.downloadUrl(), installed2.scope()); log.info("Auto-updated {}: {}", result.pluginId(), installResult.message()); } } } } catch (Exception e) { log.debug("Update check failed: {}", e.getMessage()); } } lastCheckTime = Instant.now(); // 通知回调 if (!results.isEmpty() && notificationCallback != null) { notificationCallback.onUpdatesAvailable(results); } log.info("Update check complete: {} updates available", results.size()); return results; } /** * 获取待处理的更新列表。 */ public Map getPendingUpdates() { return Map.copyOf(pendingUpdates); } /** * 清除指定插件的待更新标记。 */ public void clearPendingUpdate(String pluginId) { pendingUpdates.remove(pluginId); } /** * 是否需要检查(距上次检查超过 interval)。 */ public boolean shouldCheck() { return Duration.between(lastCheckTime, Instant.now()).compareTo(checkInterval) > 0; } // ==================== 配置 ==================== public void setAutoInstall(boolean autoInstall) { this.autoInstall = autoInstall; } public void setCheckInterval(Duration interval) { this.checkInterval = interval; } public void setNotificationCallback(UpdateNotificationCallback callback) { this.notificationCallback = callback; } public Instant getLastCheckTime() { return lastCheckTime; } // ==================== 回调接口 ==================== @FunctionalInterface public interface UpdateNotificationCallback { void onUpdatesAvailable(Map updates); } }