chore: improve geo

This commit is contained in:
hstyi
2025-06-28 11:13:26 +08:00
committed by GitHub
parent 54116a4bf5
commit fff2dd89c7
8 changed files with 22 additions and 187 deletions

View File

@@ -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")

View File

@@ -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()

View File

@@ -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")
}
}
}

View File

@@ -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
)
}
}

View File

@@ -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()

View File

@@ -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 }
}