mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-15 18:02:58 +08:00
chore: improve geo
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user