feat: support import (#127)

This commit is contained in:
hstyi
2025-01-24 16:45:36 +08:00
committed by GitHub
parent 3d0ef2a331
commit f385f4b277
5 changed files with 148 additions and 14 deletions

View File

@@ -4,9 +4,14 @@ import app.termora.AES.encodeBase64String
import app.termora.Application.ohMyJson import app.termora.Application.ohMyJson
import app.termora.actions.AnAction import app.termora.actions.AnAction
import app.termora.actions.AnActionEvent import app.termora.actions.AnActionEvent
import app.termora.highlight.KeywordHighlight
import app.termora.highlight.KeywordHighlightManager import app.termora.highlight.KeywordHighlightManager
import app.termora.keymap.Keymap
import app.termora.keymap.KeymapManager
import app.termora.keymap.KeymapPanel import app.termora.keymap.KeymapPanel
import app.termora.keymgr.KeyManager import app.termora.keymgr.KeyManager
import app.termora.keymgr.OhKeyPair
import app.termora.macro.Macro
import app.termora.macro.MacroManager import app.termora.macro.MacroManager
import app.termora.native.FileChooser import app.termora.native.FileChooser
import app.termora.sync.SyncConfig import app.termora.sync.SyncConfig
@@ -28,12 +33,11 @@ import com.sun.jna.LastErrorException
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.swing.Swing import kotlinx.coroutines.swing.Swing
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.*
import kotlinx.serialization.json.encodeToJsonElement
import kotlinx.serialization.json.put
import org.apache.commons.io.IOUtils import org.apache.commons.io.IOUtils
import org.apache.commons.lang3.StringUtils import org.apache.commons.lang3.StringUtils
import org.apache.commons.lang3.SystemUtils import org.apache.commons.lang3.SystemUtils
import org.apache.commons.lang3.exception.ExceptionUtils
import org.apache.commons.lang3.time.DateFormatUtils import org.apache.commons.lang3.time.DateFormatUtils
import org.jdesktop.swingx.JXEditorPane import org.jdesktop.swingx.JXEditorPane
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@@ -55,6 +59,11 @@ import kotlin.time.Duration.Companion.milliseconds
class SettingsOptionsPane : OptionsPane() { class SettingsOptionsPane : OptionsPane() {
private val owner get() = SwingUtilities.getWindowAncestor(this@SettingsOptionsPane) private val owner get() = SwingUtilities.getWindowAncestor(this@SettingsOptionsPane)
private val database get() = Database.getDatabase() private val database get() = Database.getDatabase()
private val hostManager get() = HostManager.getInstance()
private val keymapManager get() = KeymapManager.getInstance()
private val macroManager get() = MacroManager.getInstance()
private val keywordHighlightManager get() = KeywordHighlightManager.getInstance()
private val keyManager get() = KeyManager.getInstance()
companion object { companion object {
private val log = LoggerFactory.getLogger(SettingsOptionsPane::class.java) private val log = LoggerFactory.getLogger(SettingsOptionsPane::class.java)
@@ -499,6 +508,7 @@ class SettingsOptionsPane : OptionsPane() {
val domainTextField = OutlineTextField(255) val domainTextField = OutlineTextField(255)
val uploadConfigButton = JButton(I18n.getString("termora.settings.sync.push"), Icons.upload) val uploadConfigButton = JButton(I18n.getString("termora.settings.sync.push"), Icons.upload)
val exportConfigButton = JButton(I18n.getString("termora.settings.sync.export"), Icons.export) val exportConfigButton = JButton(I18n.getString("termora.settings.sync.export"), Icons.export)
val importConfigButton = JButton(I18n.getString("termora.settings.sync.import"), Icons.import)
val downloadConfigButton = JButton(I18n.getString("termora.settings.sync.pull"), Icons.download) val downloadConfigButton = JButton(I18n.getString("termora.settings.sync.pull"), Icons.download)
val lastSyncTimeLabel = JLabel() val lastSyncTimeLabel = JLabel()
val sync get() = database.sync val sync get() = database.sync
@@ -610,6 +620,7 @@ class SettingsOptionsPane : OptionsPane() {
} }
exportConfigButton.addActionListener { export() } exportConfigButton.addActionListener { export() }
importConfigButton.addActionListener { import() }
keysCheckBox.addActionListener { refreshButtons() } keysCheckBox.addActionListener { refreshButtons() }
hostsCheckBox.addActionListener { refreshButtons() } hostsCheckBox.addActionListener { refreshButtons() }
@@ -626,6 +637,7 @@ class SettingsOptionsPane : OptionsPane() {
|| keywordHighlightsCheckBox.isSelected || keywordHighlightsCheckBox.isSelected
uploadConfigButton.isEnabled = downloadConfigButton.isEnabled uploadConfigButton.isEnabled = downloadConfigButton.isEnabled
exportConfigButton.isEnabled = downloadConfigButton.isEnabled exportConfigButton.isEnabled = downloadConfigButton.isEnabled
importConfigButton.isEnabled = downloadConfigButton.isEnabled
} }
private fun export() { private fun export() {
@@ -641,6 +653,109 @@ class SettingsOptionsPane : OptionsPane() {
} }
} }
private fun import() {
val fileChooser = FileChooser()
fileChooser.fileSelectionMode = JFileChooser.FILES_ONLY
fileChooser.osxAllowedFileTypes = listOf("json")
fileChooser.win32Filters.add(Pair("JSON files", listOf("json")))
fileChooser.showOpenDialog(owner).thenAccept { files ->
if (files.isNotEmpty()) {
SwingUtilities.invokeLater { importFromFile(files.first()) }
}
}
}
private fun importFromFile(file: File) {
if (!file.exists()) {
return
}
val ranges = getSyncConfig().ranges
if (ranges.isEmpty()) {
return
}
// 最大 100MB
if (file.length() >= 1024 * 1024 * 100) {
OptionPane.showMessageDialog(
owner, I18n.getString("termora.settings.sync.import.file-too-large"),
messageType = JOptionPane.ERROR_MESSAGE
)
return
}
val text = file.readText()
val jsonResult = ohMyJson.runCatching { decodeFromString<JsonObject>(text) }
if (jsonResult.isFailure) {
val e = jsonResult.exceptionOrNull() ?: return
OptionPane.showMessageDialog(
owner, ExceptionUtils.getRootCauseMessage(e),
messageType = JOptionPane.ERROR_MESSAGE
)
return
}
val json = jsonResult.getOrNull() ?: return
if (ranges.contains(SyncRange.Hosts)) {
val hosts = json["hosts"]
if (hosts is JsonArray) {
ohMyJson.runCatching { decodeFromJsonElement<List<Host>>(hosts.jsonArray) }.onSuccess {
for (host in it) {
hostManager.addHost(host)
}
}
}
}
if (ranges.contains(SyncRange.KeyPairs)) {
val keyPairs = json["keyPairs"]
if (keyPairs is JsonArray) {
ohMyJson.runCatching { decodeFromJsonElement<List<OhKeyPair>>(keyPairs.jsonArray) }.onSuccess {
for (keyPair in it) {
keyManager.addOhKeyPair(keyPair)
}
}
}
}
if (ranges.contains(SyncRange.KeywordHighlights)) {
val keywordHighlights = json["keywordHighlights"]
if (keywordHighlights is JsonArray) {
ohMyJson.runCatching { decodeFromJsonElement<List<KeywordHighlight>>(keywordHighlights.jsonArray) }
.onSuccess {
for (keyPair in it) {
keywordHighlightManager.addKeywordHighlight(keyPair)
}
}
}
}
if (ranges.contains(SyncRange.Macros)) {
val macros = json["macros"]
if (macros is JsonArray) {
ohMyJson.runCatching { decodeFromJsonElement<List<Macro>>(macros.jsonArray) }.onSuccess {
for (macro in it) {
macroManager.addMacro(macro)
}
}
}
}
if (ranges.contains(SyncRange.Keymap)) {
val keymaps = json["keymaps"]
if (keymaps is JsonArray) {
for (keymap in keymaps.jsonArray.mapNotNull { Keymap.fromJSON(it.jsonObject) }) {
keymapManager.addKeymap(keymap)
}
}
}
OptionPane.showMessageDialog(
owner, I18n.getString("termora.settings.sync.import.successful"),
messageType = JOptionPane.INFORMATION_MESSAGE
)
}
private fun exportText(file: File) { private fun exportText(file: File) {
val syncConfig = getSyncConfig() val syncConfig = getSyncConfig()
val text = ohMyJson.encodeToString(buildJsonObject { val text = ohMyJson.encodeToString(buildJsonObject {
@@ -651,21 +766,29 @@ class SettingsOptionsPane : OptionsPane() {
put("os", SystemUtils.OS_NAME) put("os", SystemUtils.OS_NAME)
put("exportDateHuman", DateFormatUtils.ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT.format(Date(now))) put("exportDateHuman", DateFormatUtils.ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT.format(Date(now)))
if (syncConfig.ranges.contains(SyncRange.Hosts)) { if (syncConfig.ranges.contains(SyncRange.Hosts)) {
put("hosts", ohMyJson.encodeToJsonElement(HostManager.getInstance().hosts())) put("hosts", ohMyJson.encodeToJsonElement(hostManager.hosts()))
} }
if (syncConfig.ranges.contains(SyncRange.KeyPairs)) { if (syncConfig.ranges.contains(SyncRange.KeyPairs)) {
put("keyPairs", ohMyJson.encodeToJsonElement(KeyManager.getInstance().getOhKeyPairs())) put("keyPairs", ohMyJson.encodeToJsonElement(keyManager.getOhKeyPairs()))
} }
if (syncConfig.ranges.contains(SyncRange.KeywordHighlights)) { if (syncConfig.ranges.contains(SyncRange.KeywordHighlights)) {
put( put(
"keywordHighlights", "keywordHighlights",
ohMyJson.encodeToJsonElement(KeywordHighlightManager.getInstance().getKeywordHighlights()) ohMyJson.encodeToJsonElement(keywordHighlightManager.getKeywordHighlights())
) )
} }
if (syncConfig.ranges.contains(SyncRange.Macros)) { if (syncConfig.ranges.contains(SyncRange.Macros)) {
put( put(
"macros", "macros",
ohMyJson.encodeToJsonElement(MacroManager.getInstance().getMacros()) ohMyJson.encodeToJsonElement(macroManager.getMacros())
)
}
if (syncConfig.ranges.contains(SyncRange.Keymap)) {
val keymaps = keymapManager.getKeymaps().filter { !it.isReadonly }
.map { it.toJSONObject() }
put(
"keymaps",
ohMyJson.encodeToJsonElement(keymaps)
) )
} }
put("settings", buildJsonObject { put("settings", buildJsonObject {
@@ -710,6 +833,7 @@ class SettingsOptionsPane : OptionsPane() {
) )
} }
@Suppress("DuplicatedCode")
private suspend fun pushOrPull(push: Boolean) { private suspend fun pushOrPull(push: Boolean) {
if (typeComboBox.selectedItem == SyncType.GitLab) { if (typeComboBox.selectedItem == SyncType.GitLab) {
@@ -765,6 +889,7 @@ class SettingsOptionsPane : OptionsPane() {
withContext(Dispatchers.Swing) { withContext(Dispatchers.Swing) {
exportConfigButton.isEnabled = false exportConfigButton.isEnabled = false
importConfigButton.isEnabled = false
downloadConfigButton.isEnabled = false downloadConfigButton.isEnabled = false
uploadConfigButton.isEnabled = false uploadConfigButton.isEnabled = false
typeComboBox.isEnabled = false typeComboBox.isEnabled = false
@@ -800,6 +925,7 @@ class SettingsOptionsPane : OptionsPane() {
withContext(Dispatchers.Swing) { withContext(Dispatchers.Swing) {
downloadConfigButton.isEnabled = true downloadConfigButton.isEnabled = true
exportConfigButton.isEnabled = true exportConfigButton.isEnabled = true
importConfigButton.isEnabled = true
uploadConfigButton.isEnabled = true uploadConfigButton.isEnabled = true
keysCheckBox.isEnabled = true keysCheckBox.isEnabled = true
hostsCheckBox.isEnabled = true hostsCheckBox.isEnabled = true
@@ -940,7 +1066,7 @@ class SettingsOptionsPane : OptionsPane() {
var rows = 1 var rows = 1
val step = 2 val step = 2
val builder = FormBuilder.create().layout(layout).debug(false); val builder = FormBuilder.create().layout(layout).debug(false)
val box = Box.createHorizontalBox() val box = Box.createHorizontalBox()
box.add(typeComboBox) box.add(typeComboBox)
if (typeComboBox.selectedItem == SyncType.GitLab) { if (typeComboBox.selectedItem == SyncType.GitLab) {
@@ -959,10 +1085,11 @@ class SettingsOptionsPane : OptionsPane() {
// Sync buttons // Sync buttons
.add( .add(
FormBuilder.create() FormBuilder.create()
.layout(FormLayout("left:pref, $formMargin, left:pref, $formMargin, left:pref", "pref")) .layout(FormLayout("pref, 2dlu, pref, 2dlu, pref, 2dlu, pref", "pref"))
.add(uploadConfigButton).xy(1, 1) .add(uploadConfigButton).xy(1, 1)
.add(downloadConfigButton).xy(3, 1) .add(downloadConfigButton).xy(3, 1)
.add(exportConfigButton).xy(5, 1) .add(exportConfigButton).xy(5, 1)
.add(importConfigButton).xy(7, 1)
.build() .build()
).xy(3, rows, "center, fill").apply { rows += step } ).xy(3, rows, "center, fill").apply { rows += step }
.add(lastSyncTimeLabel).xy(3, rows, "center, fill").apply { rows += step } .add(lastSyncTimeLabel).xy(3, rows, "center, fill").apply { rows += step }
@@ -1057,8 +1184,6 @@ class SettingsOptionsPane : OptionsPane() {
private val tip = FlatLabel() private val tip = FlatLabel()
private val safeBtn = FlatButton() private val safeBtn = FlatButton()
private val doorman get() = Doorman.getInstance() private val doorman get() = Doorman.getInstance()
private val hostManager get() = HostManager.getInstance()
private val keyManager get() = KeyManager.getInstance()
init { init {
initView() initView()

View File

@@ -17,6 +17,10 @@ class FileChooser {
var allowsOtherFileTypes = true var allowsOtherFileTypes = true
var canCreateDirectories = true var canCreateDirectories = true
var win32Filters = mutableListOf<Pair<String, List<String>>>() var win32Filters = mutableListOf<Pair<String, List<String>>>()
/**
* e.g. listOf("json")
*/
var osxAllowedFileTypes = emptyList<String>() var osxAllowedFileTypes = emptyList<String>()
/** /**

View File

@@ -70,7 +70,10 @@ termora.settings.sync.push=Push
termora.settings.sync.push-warning=Pushing will overwrite the configuration. It is recommended to pull before pushing termora.settings.sync.push-warning=Pushing will overwrite the configuration. It is recommended to pull before pushing
termora.settings.sync.pull=Pull termora.settings.sync.pull=Pull
termora.settings.sync.done=Synchronized data successfully termora.settings.sync.done=Synchronized data successfully
termora.settings.sync.export=Export termora.settings.sync.export=${termora.keymgr.export}
termora.settings.sync.import=${termora.keymgr.import}
termora.settings.sync.import.file-too-large=The file is too large
termora.settings.sync.import.successful=Import data successfully
termora.settings.sync.export-done=The export was successful termora.settings.sync.export-done=The export was successful
termora.settings.sync.export-done-open-folder=The export was successful. Do you want to open the folder? termora.settings.sync.export-done-open-folder=The export was successful. Do you want to open the folder?
termora.settings.sync.range=Range termora.settings.sync.range=Range

View File

@@ -74,13 +74,14 @@ termora.settings.sync=同步
termora.settings.sync.push=推送 termora.settings.sync.push=推送
termora.settings.sync.push-warning=推送将覆盖已有配置,建议先拉取再推送 termora.settings.sync.push-warning=推送将覆盖已有配置,建议先拉取再推送
termora.settings.sync.pull=拉取 termora.settings.sync.pull=拉取
termora.settings.sync.export=导出
termora.settings.sync.export-done=导出成功 termora.settings.sync.export-done=导出成功
termora.settings.sync.export-done-open-folder=导出成功,是否需要打开所在文件夹? termora.settings.sync.export-done-open-folder=导出成功,是否需要打开所在文件夹?
termora.settings.sync.range=范围 termora.settings.sync.range=范围
termora.settings.sync.range.keys=我的密钥 termora.settings.sync.range.keys=我的密钥
termora.settings.sync.last-sync-time=最后同步时间 termora.settings.sync.last-sync-time=最后同步时间
termora.settings.sync.done=同步数据成功 termora.settings.sync.done=同步数据成功
termora.settings.sync.import.file-too-large=文件太大
termora.settings.sync.import.successful=导入数据成功
termora.settings.sync.gist=片段 termora.settings.sync.gist=片段
termora.settings.sync.token=令牌 termora.settings.sync.token=令牌
termora.settings.sync.type=类型 termora.settings.sync.type=类型

View File

@@ -78,13 +78,14 @@ termora.settings.sync=同步
termora.settings.sync.push=推送 termora.settings.sync.push=推送
termora.settings.sync.push-warning=推送將覆蓋先前的配置,建議先拉取再推送 termora.settings.sync.push-warning=推送將覆蓋先前的配置,建議先拉取再推送
termora.settings.sync.pull=拉取 termora.settings.sync.pull=拉取
termora.settings.sync.export=匯出
termora.settings.sync.export-done=匯出成功 termora.settings.sync.export-done=匯出成功
termora.settings.sync.export-done-open-folder=匯出成功,是否需要打開所在資料夾? termora.settings.sync.export-done-open-folder=匯出成功,是否需要打開所在資料夾?
termora.settings.sync.range=範圍 termora.settings.sync.range=範圍
termora.settings.sync.range.keys=我的密鑰 termora.settings.sync.range.keys=我的密鑰
termora.settings.sync.last-sync-time=最後同步時間 termora.settings.sync.last-sync-time=最後同步時間
termora.settings.sync.done=同步資料成功 termora.settings.sync.done=同步資料成功
termora.settings.sync.import.file-too-large=檔案太大
termora.settings.sync.import.successful=導入資料成功
termora.settings.sync.gist=片段 termora.settings.sync.gist=片段
termora.settings.sync.token=令牌 termora.settings.sync.token=令牌
termora.settings.sync.type=類型 termora.settings.sync.type=類型