From fff2dd89c7bc576e29019bb8237804532a0a3deb Mon Sep 17 00:00:00 2001 From: hstyi Date: Sat, 28 Jun 2025 11:13:26 +0800 Subject: [PATCH] chore: improve geo --- plugins/geo/build.gradle.kts | 4 +- .../kotlin/app/termora/plugins/geo/Geo.kt | 25 +--- .../geo/GeoApplicationRunnerExtension.kt | 108 ------------------ .../termora/plugins/geo/GeoFrameExtension.kt | 47 -------- .../geo/GeoHostTreeShowMoreEnableExtension.kt | 5 - .../app/termora/plugins/geo/GeoPlugin.kt | 4 - src/main/kotlin/app/termora/I18n.kt | 8 ++ .../plugin/marketplace/MarketplaceManager.kt | 8 +- 8 files changed, 22 insertions(+), 187 deletions(-) delete mode 100644 plugins/geo/src/main/kotlin/app/termora/plugins/geo/GeoApplicationRunnerExtension.kt delete mode 100644 plugins/geo/src/main/kotlin/app/termora/plugins/geo/GeoFrameExtension.kt diff --git a/plugins/geo/build.gradle.kts b/plugins/geo/build.gradle.kts index a7e4283..ed95100 100644 --- a/plugins/geo/build.gradle.kts +++ b/plugins/geo/build.gradle.kts @@ -2,12 +2,14 @@ plugins { alias(libs.plugins.kotlin.jvm) } -project.version = "0.0.2" +project.version = "0.0.3" dependencies { testImplementation(kotlin("test")) compileOnly(project(":")) implementation("com.maxmind.geoip2:geoip2:4.3.1") + // https://github.com/hstyi/geolite2 + implementation("com.github.hstyi:geolite2:v1.0-202506280303") } apply(from = "$rootDir/plugins/common.gradle.kts") diff --git a/plugins/geo/src/main/kotlin/app/termora/plugins/geo/Geo.kt b/plugins/geo/src/main/kotlin/app/termora/plugins/geo/Geo.kt index a9bbc71..7d58ee7 100644 --- a/plugins/geo/src/main/kotlin/app/termora/plugins/geo/Geo.kt +++ b/plugins/geo/src/main/kotlin/app/termora/plugins/geo/Geo.kt @@ -1,14 +1,12 @@ package app.termora.plugins.geo -import app.termora.Application import app.termora.ApplicationScope import app.termora.Disposable +import app.termora.geo.GeoLibrary import com.maxmind.db.CHMCache import com.maxmind.geoip2.DatabaseReader -import org.apache.commons.io.FileUtils import org.apache.commons.io.IOUtils import org.slf4j.LoggerFactory -import java.io.File import java.net.InetAddress import java.util.* import java.util.concurrent.atomic.AtomicBoolean @@ -29,28 +27,20 @@ internal class Geo private constructor() : Disposable { private var reader: DatabaseReader? = null private fun initialize() { - if (GeoApplicationRunnerExtension.instance.isReady().not()) return if (isInitialized()) return if (initialized.compareAndSet(false, true)) { try { - val database = getDatabaseFile() - if ((database.exists() && database.isFile).not()) { - throw IllegalStateException("${database.absolutePath} not be found") + val input = GeoLibrary.getInputStream() + if (input == null) { + throw IllegalStateException("GeoLite2-Country.mmdb not be found") } val locale = Locale.getDefault().toString().replace("_", "-") try { - reader = DatabaseReader.Builder(database) + reader = DatabaseReader.Builder(input) .locales(listOf(locale, "en")) .withCache(CHMCache()).build() } catch (e: Exception) { - - // 打开数据失败一般都是数据文件顺坏,删除数据库 - FileUtils.deleteQuietly(database) - - // 重新下载 - GeoApplicationRunnerExtension.instance.reload() - throw e } } catch (e: Exception) { @@ -63,11 +53,6 @@ internal class Geo private constructor() : Disposable { } - fun getDatabaseFile(): File { - val dir = FileUtils.getFile(Application.getBaseDataDir(), "config", "plugins", "geo") - return File(dir, "GeoLite2-Country.mmdb") - } - fun country(ip: String): Country? { try { initialize() diff --git a/plugins/geo/src/main/kotlin/app/termora/plugins/geo/GeoApplicationRunnerExtension.kt b/plugins/geo/src/main/kotlin/app/termora/plugins/geo/GeoApplicationRunnerExtension.kt deleted file mode 100644 index 818d2fe..0000000 --- a/plugins/geo/src/main/kotlin/app/termora/plugins/geo/GeoApplicationRunnerExtension.kt +++ /dev/null @@ -1,108 +0,0 @@ -package app.termora.plugins.geo - -import app.termora.Application -import app.termora.ApplicationRunnerExtension -import app.termora.randomUUID -import app.termora.swingCoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.swing.Swing -import kotlinx.coroutines.withContext -import okhttp3.Request -import org.apache.commons.io.FileUtils -import org.apache.commons.io.IOUtils -import org.slf4j.LoggerFactory -import java.io.File -import java.net.ProxySelector -import java.util.concurrent.TimeUnit -import kotlin.time.Duration.Companion.seconds - -class GeoApplicationRunnerExtension private constructor() : ApplicationRunnerExtension { - companion object { - private val log = LoggerFactory.getLogger(GeoApplicationRunnerExtension::class.java) - val instance = GeoApplicationRunnerExtension() - } - - private var ready = false - private val httpClient by lazy { - Application.httpClient.newBuilder() - .callTimeout(15, TimeUnit.MINUTES) - .readTimeout(10, TimeUnit.MINUTES) - .proxySelector(ProxySelector.getDefault()) - .build() - } - - override fun ready() { - - val databaseFile = Geo.getInstance().getDatabaseFile() - if (databaseFile.exists()) { - ready = true - return - } - - // 重新加载 - reload() - - } - - fun isReady() = ready - - internal fun reload() { - ready = false - - val databaseFile = Geo.getInstance().getDatabaseFile() - - swingCoroutineScope.launch(Dispatchers.IO) { - var timeout = 3 - - while (ready.not()) { - try { - FileUtils.forceMkdirParent(databaseFile) - - downloadGeoLite2(databaseFile) - - withContext(Dispatchers.Swing) { GeoHostTreeShowMoreEnableExtension.instance.updateComponentTreeUI() } - } catch (e: Exception) { - if (log.isWarnEnabled) { - log.warn(e.message, e) - } - } - delay(timeout.seconds) - timeout = timeout * 2 - } - } - } - - - private fun downloadGeoLite2(dbFile: File) { - val url = "https://git.io/GeoLite2-Country.mmdb" - val response = httpClient.newCall( - Request.Builder().get().url(url) - .build() - ).execute() - log.info("Fetched GeoLite2-Country.mmdb from {} status {}", url, response.code) - if (response.isSuccessful.not()) { - IOUtils.closeQuietly(response) - throw IllegalStateException("GeoLite2-Country.mmdb could not be downloaded, HTTP ${response.code}") - } - - val body = response.body - val input = body?.byteStream() - val file = FileUtils.getFile(Application.getTemporaryDir(), randomUUID()) - val output = file.outputStream() - - val downloaded = runCatching { IOUtils.copy(input, output) }.isSuccess - IOUtils.closeQuietly(input, output, body, response) - - log.info("Downloaded GeoLite2-Country.mmdb from {} , result: {}", url, downloaded) - - if (downloaded) { - FileUtils.moveFile(file, dbFile) - ready = true - } else { - throw IllegalStateException("GeoLite2-Country.mmdb could not be downloaded") - } - - } -} \ No newline at end of file diff --git a/plugins/geo/src/main/kotlin/app/termora/plugins/geo/GeoFrameExtension.kt b/plugins/geo/src/main/kotlin/app/termora/plugins/geo/GeoFrameExtension.kt deleted file mode 100644 index 7baedee..0000000 --- a/plugins/geo/src/main/kotlin/app/termora/plugins/geo/GeoFrameExtension.kt +++ /dev/null @@ -1,47 +0,0 @@ -package app.termora.plugins.geo - -import app.termora.EnableManager -import app.termora.FrameExtension -import app.termora.OptionPane -import app.termora.TermoraFrame -import java.awt.Window -import java.awt.event.WindowAdapter -import java.awt.event.WindowEvent -import javax.swing.JOptionPane -import javax.swing.SwingUtilities - -class GeoFrameExtension private constructor() : FrameExtension { - companion object { - val instance = GeoFrameExtension() - - private const val FIRST_KEY = "Plugins.Geo.isFirst" - } - - private val enableManager get() = EnableManager.getInstance() - - - override fun customize(frame: TermoraFrame) { - // 已经加载完毕,那么不需要提示 - if (GeoApplicationRunnerExtension.instance.isReady()) return - - // 已经提示过了,直接退出 - val isFirst = enableManager.getFlag(FIRST_KEY, true) - if (isFirst.not()) return - - frame.addWindowListener(object : WindowAdapter() { - override fun windowOpened(e: WindowEvent) { - enableManager.setFlag(FIRST_KEY, false) - frame.removeWindowListener(this) - SwingUtilities.invokeLater { showMessageDialog(frame) } - } - }) - } - - private fun showMessageDialog(window: Window) { - OptionPane.showMessageDialog( - window, - GeoI18n.getString("termora.plugins.geo.first-message"), - messageType = JOptionPane.INFORMATION_MESSAGE - ) - } -} \ No newline at end of file diff --git a/plugins/geo/src/main/kotlin/app/termora/plugins/geo/GeoHostTreeShowMoreEnableExtension.kt b/plugins/geo/src/main/kotlin/app/termora/plugins/geo/GeoHostTreeShowMoreEnableExtension.kt index 29d2fb6..3913150 100644 --- a/plugins/geo/src/main/kotlin/app/termora/plugins/geo/GeoHostTreeShowMoreEnableExtension.kt +++ b/plugins/geo/src/main/kotlin/app/termora/plugins/geo/GeoHostTreeShowMoreEnableExtension.kt @@ -1,7 +1,6 @@ package app.termora.plugins.geo import app.termora.EnableManager -import app.termora.I18n import app.termora.SwingUtils import app.termora.TermoraFrameManager import app.termora.tree.HostTreeShowMoreEnableExtension @@ -21,11 +20,7 @@ internal class GeoHostTreeShowMoreEnableExtension private constructor() : HostTr override fun createJCheckBoxMenuItem(tree: JTree): JCheckBoxMenuItem { val item = JCheckBoxMenuItem("Geo") - item.isEnabled = GeoApplicationRunnerExtension.instance.isReady() item.isSelected = item.isEnabled && enableManager.getFlag(KEY, true) - if (item.isEnabled.not()) { - item.text = GeoI18n.getString("termora.plugins.geo.coming-soon") - } item.addActionListener { enableManager.setFlag(KEY, item.isSelected) updateComponentTreeUI() diff --git a/plugins/geo/src/main/kotlin/app/termora/plugins/geo/GeoPlugin.kt b/plugins/geo/src/main/kotlin/app/termora/plugins/geo/GeoPlugin.kt index 361259f..f779aac 100644 --- a/plugins/geo/src/main/kotlin/app/termora/plugins/geo/GeoPlugin.kt +++ b/plugins/geo/src/main/kotlin/app/termora/plugins/geo/GeoPlugin.kt @@ -1,7 +1,5 @@ package app.termora.plugins.geo -import app.termora.ApplicationRunnerExtension -import app.termora.FrameExtension import app.termora.plugin.Extension import app.termora.plugin.ExtensionSupport import app.termora.plugin.Plugin @@ -12,10 +10,8 @@ class GeoPlugin : Plugin { private val support = ExtensionSupport() init { - support.addExtension(ApplicationRunnerExtension::class.java) { GeoApplicationRunnerExtension.instance } support.addExtension(SimpleTreeCellRendererExtension::class.java) { GeoSimpleTreeCellRendererExtension.instance } support.addExtension(HostTreeShowMoreEnableExtension::class.java) { GeoHostTreeShowMoreEnableExtension.instance } - support.addExtension(FrameExtension::class.java) { GeoFrameExtension.instance } } diff --git a/src/main/kotlin/app/termora/I18n.kt b/src/main/kotlin/app/termora/I18n.kt index d3b175b..9413943 100644 --- a/src/main/kotlin/app/termora/I18n.kt +++ b/src/main/kotlin/app/termora/I18n.kt @@ -1,6 +1,7 @@ package app.termora import org.apache.commons.lang3.LocaleUtils +import org.apache.commons.lang3.StringUtils import org.slf4j.Logger import org.slf4j.LoggerFactory import java.util.* @@ -33,6 +34,13 @@ object I18n : AbstractI18n() { return null } + /** + * 如果使用 zh_CN 则定义为中国大陆用户,应当优先使用国内服务器 + */ + fun isChinaMainland(): Boolean { + return StringUtils.equalsIgnoreCase(containsLanguage(Locale.getDefault()), "zh_CN") + } + fun getLanguages(): Map { return supportedLanguages } diff --git a/src/main/kotlin/app/termora/plugin/marketplace/MarketplaceManager.kt b/src/main/kotlin/app/termora/plugin/marketplace/MarketplaceManager.kt index 6eb854e..58749c2 100644 --- a/src/main/kotlin/app/termora/plugin/marketplace/MarketplaceManager.kt +++ b/src/main/kotlin/app/termora/plugin/marketplace/MarketplaceManager.kt @@ -93,8 +93,12 @@ internal class MarketplaceManager private constructor() { val version = Semver.parse(Application.getVersion()) ?: return emptyList() - val repositories = PluginRepositoryManager.getInstance().getRepositories().toMutableSet() - repositories.add("https://github.com/TermoraDev/termora-marketplace/releases/latest/download/plugins.xml") + val repositories = PluginRepositoryManager.getInstance().getRepositories().distinct().toMutableList() + if (I18n.isChinaMainland()) { + repositories.addFirst("https://plugins.termora.cn/plugins.xml") + } else { + repositories.addFirst("https://github.com/TermoraDev/termora-marketplace/releases/latest/download/plugins.xml") + } val plugins = mutableListOf() val executorService = Executors.newVirtualThreadPerTaskExecutor()