mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
feat: improve sync (#429)
This commit is contained in:
@@ -30,6 +30,7 @@ class Database private constructor(private val env: Environment) : Disposable {
|
|||||||
private const val KEYWORD_HIGHLIGHT_STORE = "KeywordHighlight"
|
private const val KEYWORD_HIGHLIGHT_STORE = "KeywordHighlight"
|
||||||
private const val MACRO_STORE = "Macro"
|
private const val MACRO_STORE = "Macro"
|
||||||
private const val KEY_PAIR_STORE = "KeyPair"
|
private const val KEY_PAIR_STORE = "KeyPair"
|
||||||
|
private const val DELETED_DATA_STORE = "DeletedData"
|
||||||
private val log = LoggerFactory.getLogger(Database::class.java)
|
private val log = LoggerFactory.getLogger(Database::class.java)
|
||||||
|
|
||||||
|
|
||||||
@@ -142,6 +143,37 @@ class Database private constructor(private val env: Environment) : Disposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeHost(id: String) {
|
||||||
|
env.executeInTransaction {
|
||||||
|
delete(it, HOST_STORE, id)
|
||||||
|
if (log.isDebugEnabled) {
|
||||||
|
log.debug("Removed host: $id")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addDeletedData(deletedData: DeletedData) {
|
||||||
|
val text = ohMyJson.encodeToString(deletedData)
|
||||||
|
env.executeInTransaction {
|
||||||
|
put(it, DELETED_DATA_STORE, deletedData.id, text)
|
||||||
|
if (log.isDebugEnabled) {
|
||||||
|
log.debug("Added DeletedData: ${deletedData.id} , $text")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDeletedData(): Collection<DeletedData> {
|
||||||
|
return env.computeInTransaction { tx ->
|
||||||
|
openCursor<DeletedData?>(tx, DELETED_DATA_STORE) { _, value ->
|
||||||
|
try {
|
||||||
|
ohMyJson.decodeFromString(value)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}.values.filterNotNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun addSnippet(snippet: Snippet) {
|
fun addSnippet(snippet: Snippet) {
|
||||||
var text = ohMyJson.encodeToString(snippet)
|
var text = ohMyJson.encodeToString(snippet)
|
||||||
if (doorman.isWorking()) {
|
if (doorman.isWorking()) {
|
||||||
@@ -155,6 +187,14 @@ class Database private constructor(private val env: Environment) : Disposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeSnippet(id: String) {
|
||||||
|
env.executeInTransaction {
|
||||||
|
delete(it, SNIPPET_STORE, id)
|
||||||
|
if (log.isDebugEnabled) {
|
||||||
|
log.debug("Removed snippet: $id")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getSnippets(): Collection<Snippet> {
|
fun getSnippets(): Collection<Snippet> {
|
||||||
val isWorking = doorman.isWorking()
|
val isWorking = doorman.isWorking()
|
||||||
|
|||||||
52
src/main/kotlin/app/termora/DeleteDataManager.kt
Normal file
52
src/main/kotlin/app/termora/DeleteDataManager.kt
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package app.termora
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仅标记
|
||||||
|
*/
|
||||||
|
class DeleteDataManager private constructor() {
|
||||||
|
companion object {
|
||||||
|
fun getInstance(): DeleteDataManager {
|
||||||
|
return ApplicationScope.forApplicationScope().getOrCreate(DeleteDataManager::class) { DeleteDataManager() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val data = mutableMapOf<String, DeletedData>()
|
||||||
|
private val database get() = Database.getDatabase()
|
||||||
|
|
||||||
|
fun removeHost(id: String, deleteDate: Long = System.currentTimeMillis()) {
|
||||||
|
addDeletedData(DeletedData(id, "Host", deleteDate))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeKeymap(id: String, deleteDate: Long = System.currentTimeMillis()) {
|
||||||
|
addDeletedData(DeletedData(id, "Keymap", deleteDate))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeKeyPair(id: String, deleteDate: Long = System.currentTimeMillis()) {
|
||||||
|
addDeletedData(DeletedData(id, "KeyPair", deleteDate))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeKeywordHighlight(id: String, deleteDate: Long = System.currentTimeMillis()) {
|
||||||
|
addDeletedData(DeletedData(id, "KeywordHighlight", deleteDate))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeMacro(id: String, deleteDate: Long = System.currentTimeMillis()) {
|
||||||
|
addDeletedData(DeletedData(id, "Macro", deleteDate))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeSnippet(id: String, deleteDate: Long = System.currentTimeMillis()) {
|
||||||
|
addDeletedData(DeletedData(id, "Snippet", deleteDate))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addDeletedData(deletedData: DeletedData) {
|
||||||
|
if (data.containsKey(deletedData.id)) return
|
||||||
|
data[deletedData.id] = deletedData
|
||||||
|
database.addDeletedData(deletedData)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDeletedData(): List<DeletedData> {
|
||||||
|
if (data.isEmpty()) {
|
||||||
|
data.putAll(database.getDeletedData().associateBy { it.id })
|
||||||
|
}
|
||||||
|
return data.values.sortedBy { it.deleteDate }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -214,6 +214,27 @@ data class EncryptedHost(
|
|||||||
var updateDate: Long = 0L,
|
var updateDate: Long = 0L,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 被删除的数据
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class DeletedData(
|
||||||
|
/**
|
||||||
|
* 被删除的 ID
|
||||||
|
*/
|
||||||
|
val id: String = StringUtils.EMPTY,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据类型:Host、Keymap、KeyPair、KeywordHighlight、Macro、Snippet
|
||||||
|
*/
|
||||||
|
val type: String = StringUtils.EMPTY,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 被删除的时间
|
||||||
|
*/
|
||||||
|
val deleteDate: Long,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Host(
|
data class Host(
|
||||||
|
|||||||
@@ -16,14 +16,20 @@ class HostManager private constructor() {
|
|||||||
*/
|
*/
|
||||||
fun addHost(host: Host) {
|
fun addHost(host: Host) {
|
||||||
assertEventDispatchThread()
|
assertEventDispatchThread()
|
||||||
database.addHost(host)
|
|
||||||
if (host.deleted) {
|
if (host.deleted) {
|
||||||
hosts.entries.removeIf { it.value.id == host.id || it.value.parentId == host.id }
|
removeHost(host.id)
|
||||||
} else {
|
} else {
|
||||||
|
database.addHost(host)
|
||||||
hosts[host.id] = host
|
hosts[host.id] = host
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeHost(id: String) {
|
||||||
|
hosts.entries.removeIf { it.value.id == id || it.value.parentId == id }
|
||||||
|
database.removeHost(id)
|
||||||
|
DeleteDataManager.getInstance().removeHost(id)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 第一次调用从数据库中获取,后续从缓存中获取
|
* 第一次调用从数据库中获取,后续从缓存中获取
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -59,11 +59,11 @@ import java.io.File
|
|||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.function.Consumer
|
||||||
import javax.swing.*
|
import javax.swing.*
|
||||||
import javax.swing.event.DocumentEvent
|
import javax.swing.event.DocumentEvent
|
||||||
import javax.swing.event.PopupMenuEvent
|
import javax.swing.event.PopupMenuEvent
|
||||||
import javax.swing.event.PopupMenuListener
|
import javax.swing.event.PopupMenuListener
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
|
||||||
|
|
||||||
|
|
||||||
class SettingsOptionsPane : OptionsPane() {
|
class SettingsOptionsPane : OptionsPane() {
|
||||||
@@ -79,7 +79,6 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
companion object {
|
companion object {
|
||||||
private val log = LoggerFactory.getLogger(SettingsOptionsPane::class.java)
|
private val log = LoggerFactory.getLogger(SettingsOptionsPane::class.java)
|
||||||
private val localShells by lazy { loadShells() }
|
private val localShells by lazy { loadShells() }
|
||||||
var pulled = false
|
|
||||||
|
|
||||||
private fun loadShells(): List<String> {
|
private fun loadShells(): List<String> {
|
||||||
val shells = mutableListOf<String>()
|
val shells = mutableListOf<String>()
|
||||||
@@ -568,10 +567,9 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
val tokenTextField = OutlinePasswordField(255)
|
val tokenTextField = OutlinePasswordField(255)
|
||||||
val gistTextField = OutlineTextField(255)
|
val gistTextField = OutlineTextField(255)
|
||||||
val domainTextField = OutlineTextField(255)
|
val domainTextField = OutlineTextField(255)
|
||||||
val uploadConfigButton = JButton(I18n.getString("termora.settings.sync.push"), Icons.upload)
|
val syncConfigButton = JButton(I18n.getString("termora.settings.sync"), Icons.download)
|
||||||
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 importConfigButton = JButton(I18n.getString("termora.settings.sync.import"), Icons.import)
|
||||||
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
|
||||||
val hostsCheckBox = JCheckBox(I18n.getString("termora.welcome.my-hosts"))
|
val hostsCheckBox = JCheckBox(I18n.getString("termora.welcome.my-hosts"))
|
||||||
@@ -591,16 +589,8 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
private fun initEvents() {
|
private fun initEvents() {
|
||||||
downloadConfigButton.addActionListener {
|
syncConfigButton.addActionListener {
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(Dispatchers.IO) { sync() }
|
||||||
pushOrPull(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadConfigButton.addActionListener {
|
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
|
||||||
pushOrPull(true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
typeComboBox.addItemListener {
|
typeComboBox.addItemListener {
|
||||||
@@ -686,17 +676,41 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun sync() {
|
||||||
|
if (!pushOrPull(false)) return
|
||||||
|
if (!pushOrPull(true)) return
|
||||||
|
|
||||||
|
withContext(Dispatchers.Swing) {
|
||||||
|
if (hostsCheckBox.isSelected) {
|
||||||
|
for (window in TermoraFrameManager.getInstance().getWindows()) {
|
||||||
|
visit(window.rootPane) {
|
||||||
|
if (it is NewHostTree) it.refreshNode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OptionPane.showMessageDialog(owner, message = I18n.getString("termora.settings.sync.done"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun visit(c: JComponent, consumer: Consumer<JComponent>) {
|
||||||
|
for (e in c.components) {
|
||||||
|
if (e is JComponent) {
|
||||||
|
consumer.accept(e)
|
||||||
|
visit(e, consumer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun refreshButtons() {
|
private fun refreshButtons() {
|
||||||
sync.rangeKeyPairs = keysCheckBox.isSelected
|
sync.rangeKeyPairs = keysCheckBox.isSelected
|
||||||
sync.rangeHosts = hostsCheckBox.isSelected
|
sync.rangeHosts = hostsCheckBox.isSelected
|
||||||
sync.rangeSnippets = snippetsCheckBox.isSelected
|
sync.rangeSnippets = snippetsCheckBox.isSelected
|
||||||
sync.rangeKeywordHighlights = keywordHighlightsCheckBox.isSelected
|
sync.rangeKeywordHighlights = keywordHighlightsCheckBox.isSelected
|
||||||
|
|
||||||
downloadConfigButton.isEnabled = keysCheckBox.isSelected || hostsCheckBox.isSelected
|
syncConfigButton.isEnabled = keysCheckBox.isSelected || hostsCheckBox.isSelected
|
||||||
|| keywordHighlightsCheckBox.isSelected
|
|| keywordHighlightsCheckBox.isSelected
|
||||||
uploadConfigButton.isEnabled = downloadConfigButton.isEnabled
|
exportConfigButton.isEnabled = syncConfigButton.isEnabled
|
||||||
exportConfigButton.isEnabled = downloadConfigButton.isEnabled
|
importConfigButton.isEnabled = syncConfigButton.isEnabled
|
||||||
importConfigButton.isEnabled = downloadConfigButton.isEnabled
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun export() {
|
private fun export() {
|
||||||
@@ -1022,8 +1036,11 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true 同步成功
|
||||||
|
*/
|
||||||
@Suppress("DuplicatedCode")
|
@Suppress("DuplicatedCode")
|
||||||
private suspend fun pushOrPull(push: Boolean) {
|
private suspend fun pushOrPull(push: Boolean): Boolean {
|
||||||
|
|
||||||
if (typeComboBox.selectedItem == SyncType.GitLab) {
|
if (typeComboBox.selectedItem == SyncType.GitLab) {
|
||||||
if (domainTextField.text.isBlank()) {
|
if (domainTextField.text.isBlank()) {
|
||||||
@@ -1031,7 +1048,7 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
domainTextField.outline = "error"
|
domainTextField.outline = "error"
|
||||||
domainTextField.requestFocusInWindow()
|
domainTextField.requestFocusInWindow()
|
||||||
}
|
}
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1040,7 +1057,7 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
tokenTextField.outline = "error"
|
tokenTextField.outline = "error"
|
||||||
tokenTextField.requestFocusInWindow()
|
tokenTextField.requestFocusInWindow()
|
||||||
}
|
}
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gistTextField.text.isBlank() && !push) {
|
if (gistTextField.text.isBlank() && !push) {
|
||||||
@@ -1048,39 +1065,13 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
gistTextField.outline = "error"
|
gistTextField.outline = "error"
|
||||||
gistTextField.requestFocusInWindow()
|
gistTextField.requestFocusInWindow()
|
||||||
}
|
}
|
||||||
return
|
return false
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 没有拉取过 && 是推送 && gistId 不为空
|
|
||||||
if (!pulled && push && gistTextField.text.isNotBlank()) {
|
|
||||||
val code = withContext(Dispatchers.Swing) {
|
|
||||||
// 提示第一次推送
|
|
||||||
OptionPane.showConfirmDialog(
|
|
||||||
owner,
|
|
||||||
I18n.getString("termora.settings.sync.push-warning"),
|
|
||||||
messageType = JOptionPane.WARNING_MESSAGE,
|
|
||||||
optionType = JOptionPane.YES_NO_CANCEL_OPTION,
|
|
||||||
options = arrayOf(
|
|
||||||
uploadConfigButton.text,
|
|
||||||
downloadConfigButton.text,
|
|
||||||
I18n.getString("termora.cancel")
|
|
||||||
),
|
|
||||||
initialValue = I18n.getString("termora.cancel")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
when (code) {
|
|
||||||
-1, JOptionPane.CANCEL_OPTION -> return
|
|
||||||
JOptionPane.NO_OPTION -> pushOrPull(false) // pull
|
|
||||||
JOptionPane.YES_OPTION -> pulled = true // force push
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.Swing) {
|
withContext(Dispatchers.Swing) {
|
||||||
exportConfigButton.isEnabled = false
|
exportConfigButton.isEnabled = false
|
||||||
importConfigButton.isEnabled = false
|
importConfigButton.isEnabled = false
|
||||||
downloadConfigButton.isEnabled = false
|
syncConfigButton.isEnabled = false
|
||||||
uploadConfigButton.isEnabled = false
|
|
||||||
typeComboBox.isEnabled = false
|
typeComboBox.isEnabled = false
|
||||||
gistTextField.isEnabled = false
|
gistTextField.isEnabled = false
|
||||||
tokenTextField.isEnabled = false
|
tokenTextField.isEnabled = false
|
||||||
@@ -1091,12 +1082,7 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
hostsCheckBox.isEnabled = false
|
hostsCheckBox.isEnabled = false
|
||||||
snippetsCheckBox.isEnabled = false
|
snippetsCheckBox.isEnabled = false
|
||||||
domainTextField.isEnabled = false
|
domainTextField.isEnabled = false
|
||||||
|
syncConfigButton.text = "${I18n.getString("termora.settings.sync")}..."
|
||||||
if (push) {
|
|
||||||
uploadConfigButton.text = "${I18n.getString("termora.settings.sync.push")}..."
|
|
||||||
} else {
|
|
||||||
downloadConfigButton.text = "${I18n.getString("termora.settings.sync.pull")}..."
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val syncConfig = getSyncConfig()
|
val syncConfig = getSyncConfig()
|
||||||
@@ -1113,10 +1099,9 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
|
|
||||||
// 恢复状态
|
// 恢复状态
|
||||||
withContext(Dispatchers.Swing) {
|
withContext(Dispatchers.Swing) {
|
||||||
downloadConfigButton.isEnabled = true
|
syncConfigButton.isEnabled = true
|
||||||
exportConfigButton.isEnabled = true
|
exportConfigButton.isEnabled = true
|
||||||
importConfigButton.isEnabled = true
|
importConfigButton.isEnabled = true
|
||||||
uploadConfigButton.isEnabled = true
|
|
||||||
keysCheckBox.isEnabled = true
|
keysCheckBox.isEnabled = true
|
||||||
hostsCheckBox.isEnabled = true
|
hostsCheckBox.isEnabled = true
|
||||||
snippetsCheckBox.isEnabled = true
|
snippetsCheckBox.isEnabled = true
|
||||||
@@ -1127,11 +1112,7 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
tokenTextField.isEnabled = true
|
tokenTextField.isEnabled = true
|
||||||
domainTextField.isEnabled = true
|
domainTextField.isEnabled = true
|
||||||
keywordHighlightsCheckBox.isEnabled = true
|
keywordHighlightsCheckBox.isEnabled = true
|
||||||
if (push) {
|
syncConfigButton.text = I18n.getString("termora.settings.sync")
|
||||||
uploadConfigButton.text = I18n.getString("termora.settings.sync.push")
|
|
||||||
} else {
|
|
||||||
downloadConfigButton.text = I18n.getString("termora.settings.sync.pull")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果失败,提示错误
|
// 如果失败,提示错误
|
||||||
@@ -1151,10 +1132,8 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
withContext(Dispatchers.Swing) {
|
withContext(Dispatchers.Swing) {
|
||||||
OptionPane.showMessageDialog(owner, message, messageType = JOptionPane.ERROR_MESSAGE)
|
OptionPane.showMessageDialog(owner, message, messageType = JOptionPane.ERROR_MESSAGE)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// pulled
|
|
||||||
if (!pulled) pulled = !push
|
|
||||||
|
|
||||||
|
} else {
|
||||||
withContext(Dispatchers.Swing) {
|
withContext(Dispatchers.Swing) {
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
sync.lastSyncTime = now
|
sync.lastSyncTime = now
|
||||||
@@ -1163,14 +1142,10 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
if (push && gistTextField.text.isBlank()) {
|
if (push && gistTextField.text.isBlank()) {
|
||||||
gistTextField.text = syncResult.map { it.config }.getOrDefault(syncConfig).gistId
|
gistTextField.text = syncResult.map { it.config }.getOrDefault(syncConfig).gistId
|
||||||
}
|
}
|
||||||
OptionPane.showMessageDialog(
|
|
||||||
owner,
|
|
||||||
message = I18n.getString("termora.settings.sync.done"),
|
|
||||||
duration = 1500.milliseconds,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return syncResult.isSuccess
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1327,11 +1302,10 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
// Sync buttons
|
// Sync buttons
|
||||||
.add(
|
.add(
|
||||||
FormBuilder.create()
|
FormBuilder.create()
|
||||||
.layout(FormLayout("pref, 2dlu, pref, 2dlu, pref, 2dlu, pref", "pref"))
|
.layout(FormLayout("pref, 2dlu, pref, 2dlu, pref", "pref"))
|
||||||
.add(uploadConfigButton).xy(1, 1)
|
.add(syncConfigButton).xy(1, 1)
|
||||||
.add(downloadConfigButton).xy(3, 1)
|
.add(exportConfigButton).xy(3, 1)
|
||||||
.add(exportConfigButton).xy(5, 1)
|
.add(importConfigButton).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 }
|
||||||
|
|||||||
@@ -197,6 +197,7 @@ open class SimpleTree : JXTree() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun importData(support: TransferSupport): Boolean {
|
override fun importData(support: TransferSupport): Boolean {
|
||||||
|
if (!support.isDrop) return false
|
||||||
val dropLocation = support.dropLocation as? JTree.DropLocation ?: return false
|
val dropLocation = support.dropLocation as? JTree.DropLocation ?: return false
|
||||||
val node = dropLocation.path.lastPathComponent as? SimpleTreeNode<*> ?: return false
|
val node = dropLocation.path.lastPathComponent as? SimpleTreeNode<*> ?: return false
|
||||||
val nodes = (support.transferable.getTransferData(MoveNodeTransferable.dataFlavor) as? List<*>)
|
val nodes = (support.transferable.getTransferData(MoveNodeTransferable.dataFlavor) as? List<*>)
|
||||||
@@ -277,7 +278,7 @@ open class SimpleTree : JXTree() {
|
|||||||
|
|
||||||
protected open fun onRenamed(node: SimpleTreeNode<*>, text: String) {}
|
protected open fun onRenamed(node: SimpleTreeNode<*>, text: String) {}
|
||||||
|
|
||||||
protected open fun refreshNode(node: SimpleTreeNode<*>) {
|
open fun refreshNode(node: SimpleTreeNode<*> = model.root) {
|
||||||
val state = TreeUtils.saveExpansionState(tree)
|
val state = TreeUtils.saveExpansionState(tree)
|
||||||
val rows = selectionRows
|
val rows = selectionRows
|
||||||
|
|
||||||
|
|||||||
@@ -62,5 +62,10 @@ data class KeywordHighlight(
|
|||||||
/**
|
/**
|
||||||
* 排序
|
* 排序
|
||||||
*/
|
*/
|
||||||
val sort: Long = System.currentTimeMillis()
|
val sort: Long = System.currentTimeMillis(),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
val updateDate: Long = System.currentTimeMillis(),
|
||||||
)
|
)
|
||||||
@@ -2,6 +2,7 @@ package app.termora.highlight
|
|||||||
|
|
||||||
import app.termora.ApplicationScope
|
import app.termora.ApplicationScope
|
||||||
import app.termora.Database
|
import app.termora.Database
|
||||||
|
import app.termora.DeleteDataManager
|
||||||
import app.termora.TerminalPanelFactory
|
import app.termora.TerminalPanelFactory
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
@@ -38,6 +39,7 @@ class KeywordHighlightManager private constructor() {
|
|||||||
database.removeKeywordHighlight(id)
|
database.removeKeywordHighlight(id)
|
||||||
keywordHighlights.remove(id)
|
keywordHighlights.remove(id)
|
||||||
TerminalPanelFactory.getInstance().repaintAll()
|
TerminalPanelFactory.getInstance().repaintAll()
|
||||||
|
DeleteDataManager.getInstance().removeKeywordHighlight(id)
|
||||||
|
|
||||||
if (log.isDebugEnabled) {
|
if (log.isDebugEnabled) {
|
||||||
log.debug("Keyword highlighter removed. {}", id)
|
log.debug("Keyword highlighter removed. {}", id)
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ class KeymapManager private constructor() : Disposable {
|
|||||||
fun removeKeymap(name: String) {
|
fun removeKeymap(name: String) {
|
||||||
keymaps.remove(name)
|
keymaps.remove(name)
|
||||||
database.removeKeymap(name)
|
database.removeKeymap(name)
|
||||||
|
DeleteDataManager.getInstance().removeKeymap(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class KeymapKeyEventDispatcher : KeyEventDispatcher {
|
private inner class KeymapKeyEventDispatcher : KeyEventDispatcher {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package app.termora.keymgr
|
|||||||
|
|
||||||
import app.termora.ApplicationScope
|
import app.termora.ApplicationScope
|
||||||
import app.termora.Database
|
import app.termora.Database
|
||||||
|
import app.termora.DeleteDataManager
|
||||||
|
|
||||||
class KeyManager private constructor() {
|
class KeyManager private constructor() {
|
||||||
companion object {
|
companion object {
|
||||||
@@ -29,6 +30,7 @@ class KeyManager private constructor() {
|
|||||||
fun removeOhKeyPair(id: String) {
|
fun removeOhKeyPair(id: String) {
|
||||||
keyPairs.removeIf { it.id == id }
|
keyPairs.removeIf { it.id == id }
|
||||||
database.removeKeyPair(id)
|
database.removeKeyPair(id)
|
||||||
|
DeleteDataManager.getInstance().removeKeyPair(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOhKeyPairs(): List<OhKeyPair> {
|
fun getOhKeyPairs(): List<OhKeyPair> {
|
||||||
@@ -39,9 +41,4 @@ class KeyManager private constructor() {
|
|||||||
return keyPairs.findLast { it.id == id }
|
return keyPairs.findLast { it.id == id }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeAll() {
|
|
||||||
keyPairs.clear()
|
|
||||||
database.removeAllKeyPair()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -15,6 +15,7 @@ data class OhKeyPair(
|
|||||||
val remark: String,
|
val remark: String,
|
||||||
val length: Int,
|
val length: Int,
|
||||||
val sort: Long,
|
val sort: Long,
|
||||||
|
val updateDate: Long = System.currentTimeMillis(),
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
val empty = OhKeyPair(String(), String(), String(), String(), String(), String(), 0, 0)
|
val empty = OhKeyPair(String(), String(), String(), String(), String(), String(), 0, 0)
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ data class Macro(
|
|||||||
* 越大越靠前
|
* 越大越靠前
|
||||||
*/
|
*/
|
||||||
val sort: Long = System.currentTimeMillis(),
|
val sort: Long = System.currentTimeMillis(),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
val updateDate: Long = System.currentTimeMillis(),
|
||||||
) {
|
) {
|
||||||
val macroByteArray by lazy { macro.decodeBase64() }
|
val macroByteArray by lazy { macro.decodeBase64() }
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ package app.termora.macro
|
|||||||
|
|
||||||
import app.termora.ApplicationScope
|
import app.termora.ApplicationScope
|
||||||
import app.termora.Database
|
import app.termora.Database
|
||||||
|
import app.termora.DeleteDataManager
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,6 +39,7 @@ class MacroManager private constructor() {
|
|||||||
fun removeMacro(id: String) {
|
fun removeMacro(id: String) {
|
||||||
database.removeMacro(id)
|
database.removeMacro(id)
|
||||||
macros.remove(id)
|
macros.remove(id)
|
||||||
|
DeleteDataManager.getInstance().removeMacro(id)
|
||||||
|
|
||||||
if (log.isDebugEnabled) {
|
if (log.isDebugEnabled) {
|
||||||
log.debug("Removed macro $id")
|
log.debug("Removed macro $id")
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package app.termora.snippet
|
|||||||
|
|
||||||
import app.termora.ApplicationScope
|
import app.termora.ApplicationScope
|
||||||
import app.termora.Database
|
import app.termora.Database
|
||||||
|
import app.termora.DeleteDataManager
|
||||||
import app.termora.assertEventDispatchThread
|
import app.termora.assertEventDispatchThread
|
||||||
|
|
||||||
|
|
||||||
@@ -20,14 +21,20 @@ class SnippetManager private constructor() {
|
|||||||
*/
|
*/
|
||||||
fun addSnippet(snippet: Snippet) {
|
fun addSnippet(snippet: Snippet) {
|
||||||
assertEventDispatchThread()
|
assertEventDispatchThread()
|
||||||
database.addSnippet(snippet)
|
|
||||||
if (snippet.deleted) {
|
if (snippet.deleted) {
|
||||||
snippets.entries.removeIf { it.value.id == snippet.id || it.value.parentId == snippet.id }
|
removeSnippet(snippet.id)
|
||||||
} else {
|
} else {
|
||||||
|
database.addSnippet(snippet)
|
||||||
snippets[snippet.id] = snippet
|
snippets[snippet.id] = snippet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeSnippet(id: String) {
|
||||||
|
snippets.entries.removeIf { it.value.id == id || it.value.parentId == id }
|
||||||
|
database.removeSnippet(id)
|
||||||
|
DeleteDataManager.getInstance().removeSnippet(id)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 第一次调用从数据库中获取,后续从缓存中获取
|
* 第一次调用从数据库中获取,后续从缓存中获取
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package app.termora.sync
|
package app.termora.sync
|
||||||
|
|
||||||
import app.termora.Application.ohMyJson
|
import app.termora.Application.ohMyJson
|
||||||
|
import app.termora.DeletedData
|
||||||
import app.termora.ResponseException
|
import app.termora.ResponseException
|
||||||
import kotlinx.serialization.json.jsonObject
|
import kotlinx.serialization.json.jsonObject
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
@@ -26,46 +27,51 @@ abstract class GitSyncer : SafetySyncer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val gistResponse = parsePullResponse(response, config)
|
val gistResponse = parsePullResponse(response, config)
|
||||||
|
val deletedData = mutableListOf<DeletedData>()
|
||||||
|
|
||||||
|
// DeletedData
|
||||||
|
gistResponse.gists.findLast { it.filename == "DeletedData" }
|
||||||
|
?.let { deletedData.addAll(decodeDeletedData(it.content, config)) }
|
||||||
|
|
||||||
// decode hosts
|
// decode hosts
|
||||||
if (config.ranges.contains(SyncRange.Hosts)) {
|
if (config.ranges.contains(SyncRange.Hosts)) {
|
||||||
gistResponse.gists.findLast { it.filename == "Hosts" }?.let {
|
gistResponse.gists.findLast { it.filename == "Hosts" }?.let {
|
||||||
decodeHosts(it.content, config)
|
decodeHosts(it.content, deletedData.filter { e -> e.type == "Host" }, config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// decode keys
|
// decode keys
|
||||||
if (config.ranges.contains(SyncRange.KeyPairs)) {
|
if (config.ranges.contains(SyncRange.KeyPairs)) {
|
||||||
gistResponse.gists.findLast { it.filename == "KeyPairs" }?.let {
|
gistResponse.gists.findLast { it.filename == "KeyPairs" }?.let {
|
||||||
decodeKeys(it.content, config)
|
decodeKeys(it.content, deletedData.filter { e -> e.type == "KeyPair" }, config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// decode keyword highlights
|
// decode keyword highlights
|
||||||
if (config.ranges.contains(SyncRange.KeywordHighlights)) {
|
if (config.ranges.contains(SyncRange.KeywordHighlights)) {
|
||||||
gistResponse.gists.findLast { it.filename == "KeywordHighlights" }?.let {
|
gistResponse.gists.findLast { it.filename == "KeywordHighlights" }?.let {
|
||||||
decodeKeywordHighlights(it.content, config)
|
decodeKeywordHighlights(it.content, deletedData.filter { e -> e.type == "KeywordHighlight" }, config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// decode macros
|
// decode macros
|
||||||
if (config.ranges.contains(SyncRange.Macros)) {
|
if (config.ranges.contains(SyncRange.Macros)) {
|
||||||
gistResponse.gists.findLast { it.filename == "Macros" }?.let {
|
gistResponse.gists.findLast { it.filename == "Macros" }?.let {
|
||||||
decodeMacros(it.content, config)
|
decodeMacros(it.content, deletedData.filter { e -> e.type == "Macro" }, config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// decode keymaps
|
// decode keymaps
|
||||||
if (config.ranges.contains(SyncRange.Macros)) {
|
if (config.ranges.contains(SyncRange.Macros)) {
|
||||||
gistResponse.gists.findLast { it.filename == "Keymaps" }?.let {
|
gistResponse.gists.findLast { it.filename == "Keymaps" }?.let {
|
||||||
decodeKeymaps(it.content, config)
|
decodeKeymaps(it.content, deletedData.filter { e -> e.type == "Keymap" }, config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// decode Snippets
|
// decode Snippets
|
||||||
if (config.ranges.contains(SyncRange.Snippets)) {
|
if (config.ranges.contains(SyncRange.Snippets)) {
|
||||||
gistResponse.gists.findLast { it.filename == "Snippets" }?.let {
|
gistResponse.gists.findLast { it.filename == "Snippets" }?.let {
|
||||||
decodeSnippets(it.content, config)
|
decodeSnippets(it.content, deletedData.filter { e -> e.type == "Snippet" }, config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +85,11 @@ abstract class GitSyncer : SafetySyncer() {
|
|||||||
|
|
||||||
|
|
||||||
override fun push(config: SyncConfig): GistResponse {
|
override fun push(config: SyncConfig): GistResponse {
|
||||||
|
|
||||||
|
if (log.isInfoEnabled) {
|
||||||
|
log.info("Type: ${config.type} , Gist: ${config.gistId} Push...")
|
||||||
|
}
|
||||||
|
|
||||||
val gistFiles = mutableListOf<GistFile>()
|
val gistFiles = mutableListOf<GistFile>()
|
||||||
// aes key
|
// aes key
|
||||||
val key = ArrayUtils.subarray(config.token.padEnd(16, '0').toByteArray(), 0, 16)
|
val key = ArrayUtils.subarray(config.token.padEnd(16, '0').toByteArray(), 0, 16)
|
||||||
@@ -142,9 +153,21 @@ abstract class GitSyncer : SafetySyncer() {
|
|||||||
throw IllegalArgumentException("No gist files found")
|
throw IllegalArgumentException("No gist files found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val deletedData = encodeDeletedData(config)
|
||||||
|
if (log.isDebugEnabled) {
|
||||||
|
log.debug("Push DeletedData: {}", deletedData)
|
||||||
|
}
|
||||||
|
gistFiles.add(GistFile("DeletedData", deletedData))
|
||||||
|
|
||||||
val request = newPushRequestBuilder(gistFiles, config).build()
|
val request = newPushRequestBuilder(gistFiles, config).build()
|
||||||
|
|
||||||
return parsePushResponse(httpClient.newCall(request).execute(), config)
|
try {
|
||||||
|
return parsePushResponse(httpClient.newCall(request).execute(), config)
|
||||||
|
} finally {
|
||||||
|
if (log.isInfoEnabled) {
|
||||||
|
log.info("Type: ${config.type} , Gist: ${config.gistId} Pushed")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun parsePullResponse(response: Response, config: SyncConfig): GistResponse {
|
open fun parsePullResponse(response: Response, config: SyncConfig): GistResponse {
|
||||||
|
|||||||
@@ -34,8 +34,9 @@ abstract class SafetySyncer : Syncer {
|
|||||||
protected val macroManager get() = MacroManager.getInstance()
|
protected val macroManager get() = MacroManager.getInstance()
|
||||||
protected val keymapManager get() = KeymapManager.getInstance()
|
protected val keymapManager get() = KeymapManager.getInstance()
|
||||||
protected val snippetManager get() = SnippetManager.getInstance()
|
protected val snippetManager get() = SnippetManager.getInstance()
|
||||||
|
protected val deleteDataManager get() = DeleteDataManager.getInstance()
|
||||||
|
|
||||||
protected fun decodeHosts(text: String, config: SyncConfig) {
|
protected fun decodeHosts(text: String, deletedData: List<DeletedData>, config: SyncConfig) {
|
||||||
// aes key
|
// aes key
|
||||||
val key = getKey(config)
|
val key = getKey(config)
|
||||||
val encryptedHosts = ohMyJson.decodeFromString<List<EncryptedHost>>(text)
|
val encryptedHosts = ohMyJson.decodeFromString<List<EncryptedHost>>(text)
|
||||||
@@ -44,9 +45,9 @@ abstract class SafetySyncer : Syncer {
|
|||||||
for (encryptedHost in encryptedHosts) {
|
for (encryptedHost in encryptedHosts) {
|
||||||
val oldHost = hosts[encryptedHost.id]
|
val oldHost = hosts[encryptedHost.id]
|
||||||
|
|
||||||
// 如果一样,则无需配置
|
// 如果本地的修改时间大于云端时间,那么跳过
|
||||||
if (oldHost != null) {
|
if (oldHost != null) {
|
||||||
if (oldHost.updateDate == encryptedHost.updateDate) {
|
if (oldHost.updateDate >= encryptedHost.updateDate) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,7 +84,6 @@ abstract class SafetySyncer : Syncer {
|
|||||||
creatorId = encryptedHost.creatorId.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
creatorId = encryptedHost.creatorId.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
||||||
createDate = encryptedHost.createDate,
|
createDate = encryptedHost.createDate,
|
||||||
updateDate = encryptedHost.updateDate,
|
updateDate = encryptedHost.updateDate,
|
||||||
deleted = encryptedHost.deleted
|
|
||||||
)
|
)
|
||||||
SwingUtilities.invokeLater { hostManager.addHost(host) }
|
SwingUtilities.invokeLater { hostManager.addHost(host) }
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -93,6 +93,12 @@ abstract class SafetySyncer : Syncer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater {
|
||||||
|
deletedData.forEach {
|
||||||
|
hostManager.removeHost(it.id)
|
||||||
|
deleteDataManager.removeHost(it.id, it.deleteDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (log.isDebugEnabled) {
|
if (log.isDebugEnabled) {
|
||||||
log.debug("Decode hosts: {}", text)
|
log.debug("Decode hosts: {}", text)
|
||||||
@@ -120,7 +126,6 @@ abstract class SafetySyncer : Syncer {
|
|||||||
encryptedHost.tunnelings =
|
encryptedHost.tunnelings =
|
||||||
ohMyJson.encodeToString(host.tunnelings).aesCBCEncrypt(key, iv).encodeBase64String()
|
ohMyJson.encodeToString(host.tunnelings).aesCBCEncrypt(key, iv).encodeBase64String()
|
||||||
encryptedHost.sort = host.sort
|
encryptedHost.sort = host.sort
|
||||||
encryptedHost.deleted = host.deleted
|
|
||||||
encryptedHost.parentId = host.parentId.aesCBCEncrypt(key, iv).encodeBase64String()
|
encryptedHost.parentId = host.parentId.aesCBCEncrypt(key, iv).encodeBase64String()
|
||||||
encryptedHost.ownerId = host.ownerId.aesCBCEncrypt(key, iv).encodeBase64String()
|
encryptedHost.ownerId = host.ownerId.aesCBCEncrypt(key, iv).encodeBase64String()
|
||||||
encryptedHost.creatorId = host.creatorId.aesCBCEncrypt(key, iv).encodeBase64String()
|
encryptedHost.creatorId = host.creatorId.aesCBCEncrypt(key, iv).encodeBase64String()
|
||||||
@@ -133,7 +138,18 @@ abstract class SafetySyncer : Syncer {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun decodeSnippets(text: String, config: SyncConfig) {
|
protected fun encodeDeletedData(config: SyncConfig): String {
|
||||||
|
return ohMyJson.encodeToString(deleteDataManager.getDeletedData())
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun decodeDeletedData(text: String, config: SyncConfig): List<DeletedData> {
|
||||||
|
val deletedData = ohMyJson.decodeFromString<List<DeletedData>>(text).toMutableList()
|
||||||
|
// 和本地融合
|
||||||
|
deletedData.addAll(deleteDataManager.getDeletedData())
|
||||||
|
return deletedData
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun decodeSnippets(text: String, deletedData: List<DeletedData>, config: SyncConfig) {
|
||||||
// aes key
|
// aes key
|
||||||
val key = getKey(config)
|
val key = getKey(config)
|
||||||
val encryptedSnippets = ohMyJson.decodeFromString<List<Snippet>>(text)
|
val encryptedSnippets = ohMyJson.decodeFromString<List<Snippet>>(text)
|
||||||
@@ -144,7 +160,7 @@ abstract class SafetySyncer : Syncer {
|
|||||||
|
|
||||||
// 如果一样,则无需配置
|
// 如果一样,则无需配置
|
||||||
if (oldHost != null) {
|
if (oldHost != null) {
|
||||||
if (oldHost.updateDate == encryptedSnippet.updateDate) {
|
if (oldHost.updateDate >= encryptedSnippet.updateDate) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -165,9 +181,15 @@ abstract class SafetySyncer : Syncer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater {
|
||||||
|
deletedData.forEach {
|
||||||
|
snippetManager.removeSnippet(it.id)
|
||||||
|
deleteDataManager.removeSnippet(it.id, it.deleteDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (log.isDebugEnabled) {
|
if (log.isDebugEnabled) {
|
||||||
log.debug("Decode hosts: {}", text)
|
log.debug("Decode Snippets: {}", text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,12 +210,20 @@ abstract class SafetySyncer : Syncer {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun decodeKeys(text: String, config: SyncConfig) {
|
protected fun decodeKeys(text: String, deletedData: List<DeletedData>, config: SyncConfig) {
|
||||||
// aes key
|
// aes key
|
||||||
val key = getKey(config)
|
val key = getKey(config)
|
||||||
val encryptedKeys = ohMyJson.decodeFromString<List<OhKeyPair>>(text)
|
val encryptedKeys = ohMyJson.decodeFromString<List<OhKeyPair>>(text)
|
||||||
|
val keys = keyManager.getOhKeyPairs().associateBy { it.id }
|
||||||
|
|
||||||
for (encryptedKey in encryptedKeys) {
|
for (encryptedKey in encryptedKeys) {
|
||||||
|
val k = keys[encryptedKey.id]
|
||||||
|
if (k != null) {
|
||||||
|
if (k.updateDate > encryptedKey.updateDate) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// aes iv
|
// aes iv
|
||||||
val iv = getIv(encryptedKey.id)
|
val iv = getIv(encryptedKey.id)
|
||||||
@@ -215,6 +245,13 @@ abstract class SafetySyncer : Syncer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater {
|
||||||
|
deletedData.forEach {
|
||||||
|
keyManager.removeOhKeyPair(it.id)
|
||||||
|
deleteDataManager.removeKeyPair(it.id, it.deleteDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (log.isDebugEnabled) {
|
if (log.isDebugEnabled) {
|
||||||
log.debug("Decode keys: {}", text)
|
log.debug("Decode keys: {}", text)
|
||||||
}
|
}
|
||||||
@@ -240,12 +277,20 @@ abstract class SafetySyncer : Syncer {
|
|||||||
return ohMyJson.encodeToString(encryptedKeys)
|
return ohMyJson.encodeToString(encryptedKeys)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun decodeKeywordHighlights(text: String, config: SyncConfig) {
|
protected fun decodeKeywordHighlights(text: String, deletedData: List<DeletedData>, config: SyncConfig) {
|
||||||
// aes key
|
// aes key
|
||||||
val key = getKey(config)
|
val key = getKey(config)
|
||||||
val encryptedKeywordHighlights = ohMyJson.decodeFromString<List<KeywordHighlight>>(text)
|
val encryptedKeywordHighlights = ohMyJson.decodeFromString<List<KeywordHighlight>>(text)
|
||||||
|
val keywordHighlights = keywordHighlightManager.getKeywordHighlights().associateBy { it.id }
|
||||||
|
|
||||||
for (e in encryptedKeywordHighlights) {
|
for (e in encryptedKeywordHighlights) {
|
||||||
|
val keywordHighlight = keywordHighlights[e.id]
|
||||||
|
if (keywordHighlight != null) {
|
||||||
|
if (keywordHighlight.updateDate >= e.updateDate) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// aes iv
|
// aes iv
|
||||||
val iv = getIv(e.id)
|
val iv = getIv(e.id)
|
||||||
@@ -262,6 +307,13 @@ abstract class SafetySyncer : Syncer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater {
|
||||||
|
deletedData.forEach {
|
||||||
|
keywordHighlightManager.removeKeywordHighlight(it.id)
|
||||||
|
deleteDataManager.removeKeywordHighlight(it.id, it.deleteDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (log.isDebugEnabled) {
|
if (log.isDebugEnabled) {
|
||||||
log.debug("Decode KeywordHighlight: {}", text)
|
log.debug("Decode KeywordHighlight: {}", text)
|
||||||
}
|
}
|
||||||
@@ -281,12 +333,19 @@ abstract class SafetySyncer : Syncer {
|
|||||||
return ohMyJson.encodeToString(keywordHighlights)
|
return ohMyJson.encodeToString(keywordHighlights)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun decodeMacros(text: String, config: SyncConfig) {
|
protected fun decodeMacros(text: String, deletedData: List<DeletedData>, config: SyncConfig) {
|
||||||
// aes key
|
// aes key
|
||||||
val key = getKey(config)
|
val key = getKey(config)
|
||||||
val encryptedMacros = ohMyJson.decodeFromString<List<Macro>>(text)
|
val encryptedMacros = ohMyJson.decodeFromString<List<Macro>>(text)
|
||||||
|
val macros = macroManager.getMacros().associateBy { it.id }
|
||||||
for (e in encryptedMacros) {
|
for (e in encryptedMacros) {
|
||||||
|
val macro = macros[e.id]
|
||||||
|
if (macro != null) {
|
||||||
|
if (macro.updateDate >= e.updateDate) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// aes iv
|
// aes iv
|
||||||
val iv = getIv(e.id)
|
val iv = getIv(e.id)
|
||||||
@@ -303,6 +362,13 @@ abstract class SafetySyncer : Syncer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater {
|
||||||
|
deletedData.forEach {
|
||||||
|
macroManager.removeMacro(it.id)
|
||||||
|
deleteDataManager.removeMacro(it.id, it.deleteDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (log.isDebugEnabled) {
|
if (log.isDebugEnabled) {
|
||||||
log.debug("Decode Macros: {}", text)
|
log.debug("Decode Macros: {}", text)
|
||||||
}
|
}
|
||||||
@@ -322,12 +388,19 @@ abstract class SafetySyncer : Syncer {
|
|||||||
return ohMyJson.encodeToString(macros)
|
return ohMyJson.encodeToString(macros)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun decodeKeymaps(text: String, config: SyncConfig) {
|
protected fun decodeKeymaps(text: String, deletedData: List<DeletedData>, config: SyncConfig) {
|
||||||
|
|
||||||
for (keymap in ohMyJson.decodeFromString<List<JsonObject>>(text).mapNotNull { Keymap.fromJSON(it) }) {
|
for (keymap in ohMyJson.decodeFromString<List<JsonObject>>(text).mapNotNull { Keymap.fromJSON(it) }) {
|
||||||
keymapManager.addKeymap(keymap)
|
keymapManager.addKeymap(keymap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater {
|
||||||
|
deletedData.forEach {
|
||||||
|
keymapManager.removeKeymap(it.id)
|
||||||
|
deleteDataManager.removeKeymap(it.id, it.deleteDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (log.isDebugEnabled) {
|
if (log.isDebugEnabled) {
|
||||||
log.debug("Decode Keymaps: {}", text)
|
log.debug("Decode Keymaps: {}", text)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package app.termora.sync
|
|||||||
|
|
||||||
import app.termora.Application.ohMyJson
|
import app.termora.Application.ohMyJson
|
||||||
import app.termora.ApplicationScope
|
import app.termora.ApplicationScope
|
||||||
|
import app.termora.DeletedData
|
||||||
import app.termora.PBKDF2
|
import app.termora.PBKDF2
|
||||||
import app.termora.ResponseException
|
import app.termora.ResponseException
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
@@ -27,6 +28,9 @@ class WebDAVSyncer private constructor() : SafetySyncer() {
|
|||||||
override fun pull(config: SyncConfig): GistResponse {
|
override fun pull(config: SyncConfig): GistResponse {
|
||||||
val response = httpClient.newCall(newRequestBuilder(config).get().build()).execute()
|
val response = httpClient.newCall(newRequestBuilder(config).get().build()).execute()
|
||||||
if (!response.isSuccessful) {
|
if (!response.isSuccessful) {
|
||||||
|
if (response.code == 404) {
|
||||||
|
return GistResponse(config, emptyList())
|
||||||
|
}
|
||||||
throw ResponseException(response.code, response)
|
throw ResponseException(response.code, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,46 +38,48 @@ class WebDAVSyncer private constructor() : SafetySyncer() {
|
|||||||
?: throw ResponseException(response.code, response)
|
?: throw ResponseException(response.code, response)
|
||||||
|
|
||||||
val json = ohMyJson.decodeFromString<JsonObject>(text)
|
val json = ohMyJson.decodeFromString<JsonObject>(text)
|
||||||
|
val deletedData = mutableListOf<DeletedData>()
|
||||||
|
json["DeletedData"]?.jsonPrimitive?.content?.let { deletedData.addAll(decodeDeletedData(it, config)) }
|
||||||
|
|
||||||
// decode hosts
|
// decode hosts
|
||||||
if (config.ranges.contains(SyncRange.Hosts)) {
|
if (config.ranges.contains(SyncRange.Hosts)) {
|
||||||
json["Hosts"]?.jsonPrimitive?.content?.let {
|
json["Hosts"]?.jsonPrimitive?.content?.let {
|
||||||
decodeHosts(it, config)
|
decodeHosts(it, deletedData.filter { e -> e.type == "Host" }, config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// decode KeyPairs
|
// decode KeyPairs
|
||||||
if (config.ranges.contains(SyncRange.KeyPairs)) {
|
if (config.ranges.contains(SyncRange.KeyPairs)) {
|
||||||
json["KeyPairs"]?.jsonPrimitive?.content?.let {
|
json["KeyPairs"]?.jsonPrimitive?.content?.let {
|
||||||
decodeKeys(it, config)
|
decodeKeys(it, deletedData.filter { e -> e.type == "KeyPair" }, config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// decode Highlights
|
// decode Highlights
|
||||||
if (config.ranges.contains(SyncRange.KeywordHighlights)) {
|
if (config.ranges.contains(SyncRange.KeywordHighlights)) {
|
||||||
json["KeywordHighlights"]?.jsonPrimitive?.content?.let {
|
json["KeywordHighlights"]?.jsonPrimitive?.content?.let {
|
||||||
decodeKeywordHighlights(it, config)
|
decodeKeywordHighlights(it, deletedData.filter { e -> e.type == "KeywordHighlight" }, config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// decode Macros
|
// decode Macros
|
||||||
if (config.ranges.contains(SyncRange.Macros)) {
|
if (config.ranges.contains(SyncRange.Macros)) {
|
||||||
json["Macros"]?.jsonPrimitive?.content?.let {
|
json["Macros"]?.jsonPrimitive?.content?.let {
|
||||||
decodeMacros(it, config)
|
decodeMacros(it, deletedData.filter { e -> e.type == "Macro" }, config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// decode Keymaps
|
// decode Keymaps
|
||||||
if (config.ranges.contains(SyncRange.Keymap)) {
|
if (config.ranges.contains(SyncRange.Keymap)) {
|
||||||
json["Keymaps"]?.jsonPrimitive?.content?.let {
|
json["Keymaps"]?.jsonPrimitive?.content?.let {
|
||||||
decodeKeymaps(it, config)
|
decodeKeymaps(it, deletedData.filter { e -> e.type == "Keymap" }, config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// decode Snippets
|
// decode Snippets
|
||||||
if (config.ranges.contains(SyncRange.Snippets)) {
|
if (config.ranges.contains(SyncRange.Snippets)) {
|
||||||
json["Snippets"]?.jsonPrimitive?.content?.let {
|
json["Snippets"]?.jsonPrimitive?.content?.let {
|
||||||
decodeSnippets(it, config)
|
decodeSnippets(it, deletedData.filter { e -> e.type == "Snippet" }, config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,6 +143,13 @@ class WebDAVSyncer private constructor() : SafetySyncer() {
|
|||||||
}
|
}
|
||||||
put("Keymaps", keymapsContent)
|
put("Keymaps", keymapsContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deletedData
|
||||||
|
val deletedData = encodeDeletedData(config)
|
||||||
|
if (log.isDebugEnabled) {
|
||||||
|
log.debug("Push DeletedData: {}", deletedData)
|
||||||
|
}
|
||||||
|
put("DeletedData", deletedData)
|
||||||
}
|
}
|
||||||
|
|
||||||
val response = httpClient.newCall(
|
val response = httpClient.newCall(
|
||||||
|
|||||||
@@ -76,9 +76,6 @@ termora.settings.terminal.auto-close-tab=Auto Close Tab
|
|||||||
termora.settings.terminal.auto-close-tab-description=Automatically close the tab when the terminal is disconnected normally
|
termora.settings.terminal.auto-close-tab-description=Automatically close the tab when the terminal is disconnected normally
|
||||||
|
|
||||||
termora.settings.sync=Sync
|
termora.settings.sync=Sync
|
||||||
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.pull=Pull
|
|
||||||
termora.settings.sync.done=Synchronized data successfully
|
termora.settings.sync.done=Synchronized data successfully
|
||||||
termora.settings.sync.export=${termora.keymgr.export}
|
termora.settings.sync.export=${termora.keymgr.export}
|
||||||
termora.settings.sync.import=${termora.keymgr.import}
|
termora.settings.sync.import=${termora.keymgr.import}
|
||||||
|
|||||||
@@ -82,9 +82,6 @@ termora.settings.terminal.auto-close-tab-description=当终端正常断开连接
|
|||||||
|
|
||||||
|
|
||||||
termora.settings.sync=同步
|
termora.settings.sync=同步
|
||||||
termora.settings.sync.push=推送
|
|
||||||
termora.settings.sync.push-warning=推送将覆盖已有配置,建议先拉取再推送
|
|
||||||
termora.settings.sync.pull=拉取
|
|
||||||
termora.settings.sync.export-done=导出成功
|
termora.settings.sync.export-done=导出成功
|
||||||
termora.settings.sync.export-encrypt=输入密码加密文件 (可选)
|
termora.settings.sync.export-encrypt=输入密码加密文件 (可选)
|
||||||
termora.settings.sync.export-done-open-folder=导出成功,是否需要打开所在文件夹?
|
termora.settings.sync.export-done-open-folder=导出成功,是否需要打开所在文件夹?
|
||||||
|
|||||||
@@ -93,9 +93,6 @@ termora.settings.terminal.auto-close-tab=自動關閉標籤
|
|||||||
termora.settings.terminal.auto-close-tab-description=當終端正常斷開連線時自動關閉標籤頁
|
termora.settings.terminal.auto-close-tab-description=當終端正常斷開連線時自動關閉標籤頁
|
||||||
|
|
||||||
termora.settings.sync=同步
|
termora.settings.sync=同步
|
||||||
termora.settings.sync.push=推送
|
|
||||||
termora.settings.sync.push-warning=推送將覆蓋先前的配置,建議先拉取再推送
|
|
||||||
termora.settings.sync.pull=拉取
|
|
||||||
termora.settings.sync.export-done=匯出成功
|
termora.settings.sync.export-done=匯出成功
|
||||||
termora.settings.sync.export-encrypt=輸入密碼加密檔案 (可選)
|
termora.settings.sync.export-encrypt=輸入密碼加密檔案 (可選)
|
||||||
termora.settings.sync.export-done-open-folder=匯出成功,是否需要打開所在資料夾?
|
termora.settings.sync.export-done-open-folder=匯出成功,是否需要打開所在資料夾?
|
||||||
|
|||||||
Reference in New Issue
Block a user