mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
chore: improve geo
This commit is contained in:
@@ -2,12 +2,14 @@ plugins {
|
|||||||
alias(libs.plugins.kotlin.jvm)
|
alias(libs.plugins.kotlin.jvm)
|
||||||
}
|
}
|
||||||
|
|
||||||
project.version = "0.0.2"
|
project.version = "0.0.3"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation(kotlin("test"))
|
testImplementation(kotlin("test"))
|
||||||
compileOnly(project(":"))
|
compileOnly(project(":"))
|
||||||
implementation("com.maxmind.geoip2:geoip2:4.3.1")
|
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")
|
apply(from = "$rootDir/plugins/common.gradle.kts")
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
package app.termora.plugins.geo
|
package app.termora.plugins.geo
|
||||||
|
|
||||||
import app.termora.Application
|
|
||||||
import app.termora.ApplicationScope
|
import app.termora.ApplicationScope
|
||||||
import app.termora.Disposable
|
import app.termora.Disposable
|
||||||
|
import app.termora.geo.GeoLibrary
|
||||||
import com.maxmind.db.CHMCache
|
import com.maxmind.db.CHMCache
|
||||||
import com.maxmind.geoip2.DatabaseReader
|
import com.maxmind.geoip2.DatabaseReader
|
||||||
import org.apache.commons.io.FileUtils
|
|
||||||
import org.apache.commons.io.IOUtils
|
import org.apache.commons.io.IOUtils
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.io.File
|
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
@@ -29,28 +27,20 @@ internal class Geo private constructor() : Disposable {
|
|||||||
private var reader: DatabaseReader? = null
|
private var reader: DatabaseReader? = null
|
||||||
|
|
||||||
private fun initialize() {
|
private fun initialize() {
|
||||||
if (GeoApplicationRunnerExtension.instance.isReady().not()) return
|
|
||||||
if (isInitialized()) return
|
if (isInitialized()) return
|
||||||
|
|
||||||
if (initialized.compareAndSet(false, true)) {
|
if (initialized.compareAndSet(false, true)) {
|
||||||
try {
|
try {
|
||||||
val database = getDatabaseFile()
|
val input = GeoLibrary.getInputStream()
|
||||||
if ((database.exists() && database.isFile).not()) {
|
if (input == null) {
|
||||||
throw IllegalStateException("${database.absolutePath} not be found")
|
throw IllegalStateException("GeoLite2-Country.mmdb not be found")
|
||||||
}
|
}
|
||||||
val locale = Locale.getDefault().toString().replace("_", "-")
|
val locale = Locale.getDefault().toString().replace("_", "-")
|
||||||
try {
|
try {
|
||||||
reader = DatabaseReader.Builder(database)
|
reader = DatabaseReader.Builder(input)
|
||||||
.locales(listOf(locale, "en"))
|
.locales(listOf(locale, "en"))
|
||||||
.withCache(CHMCache()).build()
|
.withCache(CHMCache()).build()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
||||||
// 打开数据失败一般都是数据文件顺坏,删除数据库
|
|
||||||
FileUtils.deleteQuietly(database)
|
|
||||||
|
|
||||||
// 重新下载
|
|
||||||
GeoApplicationRunnerExtension.instance.reload()
|
|
||||||
|
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} 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? {
|
fun country(ip: String): Country? {
|
||||||
try {
|
try {
|
||||||
initialize()
|
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
|
package app.termora.plugins.geo
|
||||||
|
|
||||||
import app.termora.EnableManager
|
import app.termora.EnableManager
|
||||||
import app.termora.I18n
|
|
||||||
import app.termora.SwingUtils
|
import app.termora.SwingUtils
|
||||||
import app.termora.TermoraFrameManager
|
import app.termora.TermoraFrameManager
|
||||||
import app.termora.tree.HostTreeShowMoreEnableExtension
|
import app.termora.tree.HostTreeShowMoreEnableExtension
|
||||||
@@ -21,11 +20,7 @@ internal class GeoHostTreeShowMoreEnableExtension private constructor() : HostTr
|
|||||||
|
|
||||||
override fun createJCheckBoxMenuItem(tree: JTree): JCheckBoxMenuItem {
|
override fun createJCheckBoxMenuItem(tree: JTree): JCheckBoxMenuItem {
|
||||||
val item = JCheckBoxMenuItem("Geo")
|
val item = JCheckBoxMenuItem("Geo")
|
||||||
item.isEnabled = GeoApplicationRunnerExtension.instance.isReady()
|
|
||||||
item.isSelected = item.isEnabled && enableManager.getFlag(KEY, true)
|
item.isSelected = item.isEnabled && enableManager.getFlag(KEY, true)
|
||||||
if (item.isEnabled.not()) {
|
|
||||||
item.text = GeoI18n.getString("termora.plugins.geo.coming-soon")
|
|
||||||
}
|
|
||||||
item.addActionListener {
|
item.addActionListener {
|
||||||
enableManager.setFlag(KEY, item.isSelected)
|
enableManager.setFlag(KEY, item.isSelected)
|
||||||
updateComponentTreeUI()
|
updateComponentTreeUI()
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package app.termora.plugins.geo
|
package app.termora.plugins.geo
|
||||||
|
|
||||||
import app.termora.ApplicationRunnerExtension
|
|
||||||
import app.termora.FrameExtension
|
|
||||||
import app.termora.plugin.Extension
|
import app.termora.plugin.Extension
|
||||||
import app.termora.plugin.ExtensionSupport
|
import app.termora.plugin.ExtensionSupport
|
||||||
import app.termora.plugin.Plugin
|
import app.termora.plugin.Plugin
|
||||||
@@ -12,10 +10,8 @@ class GeoPlugin : Plugin {
|
|||||||
private val support = ExtensionSupport()
|
private val support = ExtensionSupport()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
support.addExtension(ApplicationRunnerExtension::class.java) { GeoApplicationRunnerExtension.instance }
|
|
||||||
support.addExtension(SimpleTreeCellRendererExtension::class.java) { GeoSimpleTreeCellRendererExtension.instance }
|
support.addExtension(SimpleTreeCellRendererExtension::class.java) { GeoSimpleTreeCellRendererExtension.instance }
|
||||||
support.addExtension(HostTreeShowMoreEnableExtension::class.java) { GeoHostTreeShowMoreEnableExtension.instance }
|
support.addExtension(HostTreeShowMoreEnableExtension::class.java) { GeoHostTreeShowMoreEnableExtension.instance }
|
||||||
support.addExtension(FrameExtension::class.java) { GeoFrameExtension.instance }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package app.termora
|
package app.termora
|
||||||
|
|
||||||
import org.apache.commons.lang3.LocaleUtils
|
import org.apache.commons.lang3.LocaleUtils
|
||||||
|
import org.apache.commons.lang3.StringUtils
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -33,6 +34,13 @@ object I18n : AbstractI18n() {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果使用 zh_CN 则定义为中国大陆用户,应当优先使用国内服务器
|
||||||
|
*/
|
||||||
|
fun isChinaMainland(): Boolean {
|
||||||
|
return StringUtils.equalsIgnoreCase(containsLanguage(Locale.getDefault()), "zh_CN")
|
||||||
|
}
|
||||||
|
|
||||||
fun getLanguages(): Map<String, String> {
|
fun getLanguages(): Map<String, String> {
|
||||||
return supportedLanguages
|
return supportedLanguages
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,8 +93,12 @@ internal class MarketplaceManager private constructor() {
|
|||||||
val version = Semver.parse(Application.getVersion())
|
val version = Semver.parse(Application.getVersion())
|
||||||
?: return emptyList()
|
?: return emptyList()
|
||||||
|
|
||||||
val repositories = PluginRepositoryManager.getInstance().getRepositories().toMutableSet()
|
val repositories = PluginRepositoryManager.getInstance().getRepositories().distinct().toMutableList()
|
||||||
repositories.add("https://github.com/TermoraDev/termora-marketplace/releases/latest/download/plugins.xml")
|
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<MarketplacePlugin>()
|
val plugins = mutableListOf<MarketplacePlugin>()
|
||||||
val executorService = Executors.newVirtualThreadPerTaskExecutor()
|
val executorService = Executors.newVirtualThreadPerTaskExecutor()
|
||||||
|
|||||||
Reference in New Issue
Block a user