mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
feat: support for WebDAV (#150)
This commit is contained in:
@@ -550,12 +550,6 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeComboBox.selectedItem == SyncType.Gitee) {
|
|
||||||
gistTextField.trailingComponent = null
|
|
||||||
} else {
|
|
||||||
gistTextField.trailingComponent = visitGistBtn
|
|
||||||
}
|
|
||||||
|
|
||||||
removeAll()
|
removeAll()
|
||||||
add(getCenterComponent(), BorderLayout.CENTER)
|
add(getCenterComponent(), BorderLayout.CENTER)
|
||||||
revalidate()
|
revalidate()
|
||||||
@@ -987,6 +981,7 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
typeComboBox.addItem(SyncType.GitHub)
|
typeComboBox.addItem(SyncType.GitHub)
|
||||||
typeComboBox.addItem(SyncType.GitLab)
|
typeComboBox.addItem(SyncType.GitLab)
|
||||||
typeComboBox.addItem(SyncType.Gitee)
|
typeComboBox.addItem(SyncType.Gitee)
|
||||||
|
typeComboBox.addItem(SyncType.WebDAV)
|
||||||
|
|
||||||
hostsCheckBox.isFocusable = false
|
hostsCheckBox.isFocusable = false
|
||||||
keysCheckBox.isFocusable = false
|
keysCheckBox.isFocusable = false
|
||||||
@@ -1005,7 +1000,31 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
tokenTextField.text = sync.token
|
tokenTextField.text = sync.token
|
||||||
domainTextField.trailingComponent = JButton(Icons.externalLink).apply {
|
domainTextField.trailingComponent = JButton(Icons.externalLink).apply {
|
||||||
addActionListener {
|
addActionListener {
|
||||||
Application.browse(URI.create("https://docs.gitlab.com/ee/api/snippets.html"))
|
if (typeComboBox.selectedItem == SyncType.GitLab) {
|
||||||
|
Application.browse(URI.create("https://docs.gitlab.com/ee/api/snippets.html"))
|
||||||
|
|
||||||
|
} else if (typeComboBox.selectedItem == SyncType.WebDAV) {
|
||||||
|
val url = domainTextField.text
|
||||||
|
if (url.isNullOrBlank()) {
|
||||||
|
OptionPane.showMessageDialog(
|
||||||
|
owner,
|
||||||
|
I18n.getString("termora.settings.sync.webdav.help")
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val uri = URI.create(url)
|
||||||
|
val sb = StringBuilder()
|
||||||
|
sb.append(uri.scheme).append("://")
|
||||||
|
if (tokenTextField.password.isNotEmpty() && gistTextField.text.isNotBlank()) {
|
||||||
|
sb.append(String(tokenTextField.password)).append(":").append(gistTextField.text)
|
||||||
|
sb.append('@')
|
||||||
|
}
|
||||||
|
sb.append(uri.authority).append(uri.path)
|
||||||
|
if (!uri.query.isNullOrBlank()) {
|
||||||
|
sb.append('?').append(uri.query)
|
||||||
|
}
|
||||||
|
Application.browse(URI.create(sb.toString()))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1015,12 +1034,15 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
|
|
||||||
tokenTextField.trailingComponent = if (tokenTextField.password.isEmpty()) getTokenBtn else null
|
tokenTextField.trailingComponent = if (tokenTextField.password.isEmpty()) getTokenBtn else null
|
||||||
|
|
||||||
if (typeComboBox.selectedItem == SyncType.GitLab) {
|
if (domainTextField.text.isBlank()) {
|
||||||
if (domainTextField.text.isBlank()) {
|
if (typeComboBox.selectedItem == SyncType.GitLab) {
|
||||||
domainTextField.text = StringUtils.defaultIfBlank(sync.domain, "https://gitlab.com/api")
|
domainTextField.text = StringUtils.defaultIfBlank(sync.domain, "https://gitlab.com/api")
|
||||||
|
} else if (typeComboBox.selectedItem == SyncType.WebDAV) {
|
||||||
|
domainTextField.text = sync.domain
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val lastSyncTime = sync.lastSyncTime
|
val lastSyncTime = sync.lastSyncTime
|
||||||
lastSyncTimeLabel.text = "${I18n.getString("termora.settings.sync.last-sync-time")}: ${
|
lastSyncTimeLabel.text = "${I18n.getString("termora.settings.sync.last-sync-time")}: ${
|
||||||
if (lastSyncTime > 0) DateFormatUtils.format(
|
if (lastSyncTime > 0) DateFormatUtils.format(
|
||||||
@@ -1069,17 +1091,37 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
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 || typeComboBox.selectedItem == SyncType.WebDAV) {
|
||||||
box.add(Box.createHorizontalStrut(4))
|
box.add(Box.createHorizontalStrut(4))
|
||||||
box.add(domainTextField)
|
box.add(domainTextField)
|
||||||
}
|
}
|
||||||
builder.add("${I18n.getString("termora.settings.sync.type")}:").xy(1, rows)
|
builder.add("${I18n.getString("termora.settings.sync.type")}:").xy(1, rows)
|
||||||
.add(box).xy(3, rows).apply { rows += step }
|
.add(box).xy(3, rows).apply { rows += step }
|
||||||
|
|
||||||
builder.add("${I18n.getString("termora.settings.sync.token")}:").xy(1, rows)
|
val isWebDAV = typeComboBox.selectedItem == SyncType.WebDAV
|
||||||
.add(tokenTextField).xy(3, rows).apply { rows += step }
|
|
||||||
.add("${I18n.getString("termora.settings.sync.gist")}:").xy(1, rows)
|
val tokenText = if (isWebDAV) {
|
||||||
.add(gistTextField).xy(3, rows).apply { rows += step }
|
I18n.getString("termora.new-host.general.username")
|
||||||
|
} else {
|
||||||
|
I18n.getString("termora.settings.sync.token")
|
||||||
|
}
|
||||||
|
|
||||||
|
val gistText = if (isWebDAV) {
|
||||||
|
I18n.getString("termora.new-host.general.password")
|
||||||
|
} else {
|
||||||
|
I18n.getString("termora.settings.sync.gist")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeComboBox.selectedItem == SyncType.Gitee || isWebDAV) {
|
||||||
|
gistTextField.trailingComponent = null
|
||||||
|
} else {
|
||||||
|
gistTextField.trailingComponent = visitGistBtn
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.add("${tokenText}:").xy(1, rows)
|
||||||
|
.add(if (isWebDAV) gistTextField else tokenTextField).xy(3, rows).apply { rows += step }
|
||||||
|
.add("${gistText}:").xy(1, rows)
|
||||||
|
.add(if (isWebDAV) tokenTextField else gistTextField).xy(3, rows).apply { rows += step }
|
||||||
.add("${I18n.getString("termora.settings.sync.range")}:").xy(1, rows)
|
.add("${I18n.getString("termora.settings.sync.range")}:").xy(1, rows)
|
||||||
.add(rangeBox).xy(3, rows).apply { rows += step }
|
.add(rangeBox).xy(3, rows).apply { rows += step }
|
||||||
// Sync buttons
|
// Sync buttons
|
||||||
|
|||||||
@@ -1,42 +1,19 @@
|
|||||||
package app.termora.sync
|
package app.termora.sync
|
||||||
|
|
||||||
import app.termora.*
|
|
||||||
import app.termora.AES.CBC.aesCBCDecrypt
|
|
||||||
import app.termora.AES.CBC.aesCBCEncrypt
|
|
||||||
import app.termora.AES.decodeBase64
|
|
||||||
import app.termora.AES.encodeBase64String
|
|
||||||
import app.termora.Application.ohMyJson
|
import app.termora.Application.ohMyJson
|
||||||
import app.termora.highlight.KeywordHighlight
|
import app.termora.ResponseException
|
||||||
import app.termora.highlight.KeywordHighlightManager
|
|
||||||
import app.termora.keymap.Keymap
|
|
||||||
import app.termora.keymap.KeymapManager
|
|
||||||
import app.termora.keymgr.KeyManager
|
|
||||||
import app.termora.keymgr.OhKeyPair
|
|
||||||
import app.termora.macro.Macro
|
|
||||||
import app.termora.macro.MacroManager
|
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.JsonObject
|
|
||||||
import kotlinx.serialization.json.jsonObject
|
import kotlinx.serialization.json.jsonObject
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.apache.commons.lang3.ArrayUtils
|
import org.apache.commons.lang3.ArrayUtils
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import javax.swing.SwingUtilities
|
|
||||||
|
|
||||||
abstract class GitSyncer : Syncer {
|
abstract class GitSyncer : SafetySyncer() {
|
||||||
companion object {
|
companion object {
|
||||||
private val log = LoggerFactory.getLogger(GitSyncer::class.java)
|
private val log = LoggerFactory.getLogger(GitSyncer::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected val description = "${Application.getName()} config"
|
|
||||||
protected val httpClient get() = Application.httpClient
|
|
||||||
protected val hostManager get() = HostManager.getInstance()
|
|
||||||
protected val keyManager get() = KeyManager.getInstance()
|
|
||||||
protected val keywordHighlightManager get() = KeywordHighlightManager.getInstance()
|
|
||||||
protected val macroManager get() = MacroManager.getInstance()
|
|
||||||
protected val keymapManager get() = KeymapManager.getInstance()
|
|
||||||
|
|
||||||
override fun pull(config: SyncConfig): GistResponse {
|
override fun pull(config: SyncConfig): GistResponse {
|
||||||
|
|
||||||
if (log.isInfoEnabled) {
|
if (log.isInfoEnabled) {
|
||||||
@@ -92,174 +69,6 @@ abstract class GitSyncer : Syncer {
|
|||||||
return gistResponse
|
return gistResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun decodeHosts(text: String, config: SyncConfig) {
|
|
||||||
// aes key
|
|
||||||
val key = getKey(config)
|
|
||||||
val encryptedHosts = ohMyJson.decodeFromString<List<EncryptedHost>>(text)
|
|
||||||
val hosts = hostManager.hosts().associateBy { it.id }
|
|
||||||
|
|
||||||
for (encryptedHost in encryptedHosts) {
|
|
||||||
val oldHost = hosts[encryptedHost.id]
|
|
||||||
|
|
||||||
// 如果一样,则无需配置
|
|
||||||
if (oldHost != null) {
|
|
||||||
if (oldHost.updateDate == encryptedHost.updateDate) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// aes iv
|
|
||||||
val iv = getIv(encryptedHost.id)
|
|
||||||
val host = Host(
|
|
||||||
id = encryptedHost.id,
|
|
||||||
name = encryptedHost.name.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
|
||||||
protocol = Protocol.valueOf(
|
|
||||||
encryptedHost.protocol.decodeBase64().aesCBCDecrypt(key, iv).decodeToString()
|
|
||||||
),
|
|
||||||
host = encryptedHost.host.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
|
||||||
port = encryptedHost.port.decodeBase64().aesCBCDecrypt(key, iv)
|
|
||||||
.decodeToString().toIntOrNull() ?: 0,
|
|
||||||
username = encryptedHost.username.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
|
||||||
remark = encryptedHost.remark.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
|
||||||
authentication = ohMyJson.decodeFromString(
|
|
||||||
encryptedHost.authentication.decodeBase64().aesCBCDecrypt(key, iv).decodeToString()
|
|
||||||
),
|
|
||||||
proxy = ohMyJson.decodeFromString(
|
|
||||||
encryptedHost.proxy.decodeBase64().aesCBCDecrypt(key, iv).decodeToString()
|
|
||||||
),
|
|
||||||
options = ohMyJson.decodeFromString(
|
|
||||||
encryptedHost.options.decodeBase64().aesCBCDecrypt(key, iv).decodeToString()
|
|
||||||
),
|
|
||||||
tunnelings = ohMyJson.decodeFromString(
|
|
||||||
encryptedHost.tunnelings.decodeBase64().aesCBCDecrypt(key, iv).decodeToString()
|
|
||||||
),
|
|
||||||
sort = encryptedHost.sort,
|
|
||||||
parentId = encryptedHost.parentId.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
|
||||||
ownerId = encryptedHost.ownerId.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
|
||||||
creatorId = encryptedHost.creatorId.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
|
||||||
createDate = encryptedHost.createDate,
|
|
||||||
updateDate = encryptedHost.updateDate,
|
|
||||||
deleted = encryptedHost.deleted
|
|
||||||
)
|
|
||||||
SwingUtilities.invokeLater { hostManager.addHost(host) }
|
|
||||||
} catch (e: Exception) {
|
|
||||||
if (log.isWarnEnabled) {
|
|
||||||
log.warn("Decode host: ${encryptedHost.id} failed. error: {}", e.message, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (log.isDebugEnabled) {
|
|
||||||
log.debug("Decode hosts: {}", text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun decodeKeys(text: String, config: SyncConfig) {
|
|
||||||
// aes key
|
|
||||||
val key = getKey(config)
|
|
||||||
val encryptedKeys = ohMyJson.decodeFromString<List<OhKeyPair>>(text)
|
|
||||||
|
|
||||||
for (encryptedKey in encryptedKeys) {
|
|
||||||
try {
|
|
||||||
// aes iv
|
|
||||||
val iv = getIv(encryptedKey.id)
|
|
||||||
val keyPair = OhKeyPair(
|
|
||||||
id = encryptedKey.id,
|
|
||||||
publicKey = encryptedKey.publicKey.decodeBase64().aesCBCDecrypt(key, iv).encodeBase64String(),
|
|
||||||
privateKey = encryptedKey.privateKey.decodeBase64().aesCBCDecrypt(key, iv).encodeBase64String(),
|
|
||||||
type = encryptedKey.type.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
|
||||||
name = encryptedKey.name.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
|
||||||
remark = encryptedKey.remark.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
|
||||||
length = encryptedKey.length,
|
|
||||||
sort = encryptedKey.sort
|
|
||||||
)
|
|
||||||
SwingUtilities.invokeLater { keyManager.addOhKeyPair(keyPair) }
|
|
||||||
} catch (e: Exception) {
|
|
||||||
if (log.isWarnEnabled) {
|
|
||||||
log.warn("Decode key: ${encryptedKey.id} failed. error: {}", e.message, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (log.isDebugEnabled) {
|
|
||||||
log.debug("Decode keys: {}", text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun decodeKeywordHighlights(text: String, config: SyncConfig) {
|
|
||||||
// aes key
|
|
||||||
val key = getKey(config)
|
|
||||||
val encryptedKeywordHighlights = ohMyJson.decodeFromString<List<KeywordHighlight>>(text)
|
|
||||||
|
|
||||||
for (e in encryptedKeywordHighlights) {
|
|
||||||
try {
|
|
||||||
// aes iv
|
|
||||||
val iv = getIv(e.id)
|
|
||||||
keywordHighlightManager.addKeywordHighlight(
|
|
||||||
e.copy(
|
|
||||||
keyword = e.keyword.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
|
||||||
description = e.description.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} catch (ex: Exception) {
|
|
||||||
if (log.isWarnEnabled) {
|
|
||||||
log.warn("Decode KeywordHighlight: ${e.id} failed. error: {}", ex.message, ex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (log.isDebugEnabled) {
|
|
||||||
log.debug("Decode KeywordHighlight: {}", text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun decodeMacros(text: String, config: SyncConfig) {
|
|
||||||
// aes key
|
|
||||||
val key = getKey(config)
|
|
||||||
val encryptedMacros = ohMyJson.decodeFromString<List<Macro>>(text)
|
|
||||||
|
|
||||||
for (e in encryptedMacros) {
|
|
||||||
try {
|
|
||||||
// aes iv
|
|
||||||
val iv = getIv(e.id)
|
|
||||||
macroManager.addMacro(
|
|
||||||
e.copy(
|
|
||||||
name = e.name.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
|
||||||
macro = e.macro.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} catch (ex: Exception) {
|
|
||||||
if (log.isWarnEnabled) {
|
|
||||||
log.warn("Decode Macro: ${e.id} failed. error: {}", ex.message, ex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (log.isDebugEnabled) {
|
|
||||||
log.debug("Decode Macros: {}", text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun decodeKeymaps(text: String, config: SyncConfig) {
|
|
||||||
|
|
||||||
for (keymap in ohMyJson.decodeFromString<List<JsonObject>>(text).mapNotNull { Keymap.fromJSON(it) }) {
|
|
||||||
keymapManager.addKeymap(keymap)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (log.isDebugEnabled) {
|
|
||||||
log.debug("Decode Keymaps: {}", text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getKey(config: SyncConfig): ByteArray {
|
|
||||||
return ArrayUtils.subarray(config.token.padEnd(16, '0').toByteArray(), 0, 16)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getIv(id: String): ByteArray {
|
|
||||||
return ArrayUtils.subarray(id.padEnd(16, '0').toByteArray(), 0, 16)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun push(config: SyncConfig): GistResponse {
|
override fun push(config: SyncConfig): GistResponse {
|
||||||
val gistFiles = mutableListOf<GistFile>()
|
val gistFiles = mutableListOf<GistFile>()
|
||||||
@@ -268,62 +77,16 @@ abstract class GitSyncer : Syncer {
|
|||||||
|
|
||||||
// Hosts
|
// Hosts
|
||||||
if (config.ranges.contains(SyncRange.Hosts)) {
|
if (config.ranges.contains(SyncRange.Hosts)) {
|
||||||
val encryptedHosts = mutableListOf<EncryptedHost>()
|
val hostsContent = encodeHosts(key)
|
||||||
for (host in hostManager.hosts()) {
|
|
||||||
// aes iv
|
|
||||||
val iv = ArrayUtils.subarray(host.id.padEnd(16, '0').toByteArray(), 0, 16)
|
|
||||||
val encryptedHost = EncryptedHost()
|
|
||||||
encryptedHost.id = host.id
|
|
||||||
encryptedHost.name = host.name.aesCBCEncrypt(key, iv).encodeBase64String()
|
|
||||||
encryptedHost.protocol = host.protocol.name.aesCBCEncrypt(key, iv).encodeBase64String()
|
|
||||||
encryptedHost.host = host.host.aesCBCEncrypt(key, iv).encodeBase64String()
|
|
||||||
encryptedHost.port = "${host.port}".aesCBCEncrypt(key, iv).encodeBase64String()
|
|
||||||
encryptedHost.username = host.username.aesCBCEncrypt(key, iv).encodeBase64String()
|
|
||||||
encryptedHost.remark = host.remark.aesCBCEncrypt(key, iv).encodeBase64String()
|
|
||||||
encryptedHost.authentication = ohMyJson.encodeToString(host.authentication)
|
|
||||||
.aesCBCEncrypt(key, iv).encodeBase64String()
|
|
||||||
encryptedHost.proxy = ohMyJson.encodeToString(host.proxy).aesCBCEncrypt(key, iv).encodeBase64String()
|
|
||||||
encryptedHost.options =
|
|
||||||
ohMyJson.encodeToString(host.options).aesCBCEncrypt(key, iv).encodeBase64String()
|
|
||||||
encryptedHost.tunnelings =
|
|
||||||
ohMyJson.encodeToString(host.tunnelings).aesCBCEncrypt(key, iv).encodeBase64String()
|
|
||||||
encryptedHost.sort = host.sort
|
|
||||||
encryptedHost.deleted = host.deleted
|
|
||||||
encryptedHost.parentId = host.parentId.aesCBCEncrypt(key, iv).encodeBase64String()
|
|
||||||
encryptedHost.ownerId = host.ownerId.aesCBCEncrypt(key, iv).encodeBase64String()
|
|
||||||
encryptedHost.creatorId = host.creatorId.aesCBCEncrypt(key, iv).encodeBase64String()
|
|
||||||
encryptedHost.createDate = host.createDate
|
|
||||||
encryptedHost.updateDate = host.updateDate
|
|
||||||
encryptedHosts.add(encryptedHost)
|
|
||||||
}
|
|
||||||
|
|
||||||
val hostsContent = ohMyJson.encodeToString(encryptedHosts)
|
|
||||||
if (log.isDebugEnabled) {
|
if (log.isDebugEnabled) {
|
||||||
log.debug("Push encryptedHosts: {}", hostsContent)
|
log.debug("Push encryptedHosts: {}", hostsContent)
|
||||||
}
|
}
|
||||||
gistFiles.add(GistFile("Hosts", hostsContent))
|
gistFiles.add(GistFile("Hosts", hostsContent))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeyPairs
|
// KeyPairs
|
||||||
if (config.ranges.contains(SyncRange.KeyPairs)) {
|
if (config.ranges.contains(SyncRange.KeyPairs)) {
|
||||||
val encryptedKeys = mutableListOf<OhKeyPair>()
|
val keysContent = encodeKeys(key)
|
||||||
for (keyPair in keyManager.getOhKeyPairs()) {
|
|
||||||
// aes iv
|
|
||||||
val iv = ArrayUtils.subarray(keyPair.id.padEnd(16, '0').toByteArray(), 0, 16)
|
|
||||||
val encryptedKeyPair = OhKeyPair(
|
|
||||||
id = keyPair.id,
|
|
||||||
publicKey = keyPair.publicKey.decodeBase64().aesCBCEncrypt(key, iv).encodeBase64String(),
|
|
||||||
privateKey = keyPair.privateKey.decodeBase64().aesCBCEncrypt(key, iv).encodeBase64String(),
|
|
||||||
type = keyPair.type.aesCBCEncrypt(key, iv).encodeBase64String(),
|
|
||||||
name = keyPair.name.aesCBCEncrypt(key, iv).encodeBase64String(),
|
|
||||||
remark = keyPair.remark.aesCBCEncrypt(key, iv).encodeBase64String(),
|
|
||||||
length = keyPair.length,
|
|
||||||
sort = keyPair.sort
|
|
||||||
)
|
|
||||||
encryptedKeys.add(encryptedKeyPair)
|
|
||||||
}
|
|
||||||
val keysContent = ohMyJson.encodeToString(encryptedKeys)
|
|
||||||
if (log.isDebugEnabled) {
|
if (log.isDebugEnabled) {
|
||||||
log.debug("Push encryptedKeys: {}", keysContent)
|
log.debug("Push encryptedKeys: {}", keysContent)
|
||||||
}
|
}
|
||||||
@@ -332,17 +95,7 @@ abstract class GitSyncer : Syncer {
|
|||||||
|
|
||||||
// Highlights
|
// Highlights
|
||||||
if (config.ranges.contains(SyncRange.KeywordHighlights)) {
|
if (config.ranges.contains(SyncRange.KeywordHighlights)) {
|
||||||
val keywordHighlights = mutableListOf<KeywordHighlight>()
|
val keywordHighlightsContent = encodeKeywordHighlights(key)
|
||||||
for (keywordHighlight in keywordHighlightManager.getKeywordHighlights()) {
|
|
||||||
// aes iv
|
|
||||||
val iv = getIv(keywordHighlight.id)
|
|
||||||
val encryptedKeyPair = keywordHighlight.copy(
|
|
||||||
keyword = keywordHighlight.keyword.aesCBCEncrypt(key, iv).encodeBase64String(),
|
|
||||||
description = keywordHighlight.description.aesCBCEncrypt(key, iv).encodeBase64String(),
|
|
||||||
)
|
|
||||||
keywordHighlights.add(encryptedKeyPair)
|
|
||||||
}
|
|
||||||
val keywordHighlightsContent = ohMyJson.encodeToString(keywordHighlights)
|
|
||||||
if (log.isDebugEnabled) {
|
if (log.isDebugEnabled) {
|
||||||
log.debug("Push keywordHighlights: {}", keywordHighlightsContent)
|
log.debug("Push keywordHighlights: {}", keywordHighlightsContent)
|
||||||
}
|
}
|
||||||
@@ -351,17 +104,7 @@ abstract class GitSyncer : Syncer {
|
|||||||
|
|
||||||
// Macros
|
// Macros
|
||||||
if (config.ranges.contains(SyncRange.Macros)) {
|
if (config.ranges.contains(SyncRange.Macros)) {
|
||||||
val macros = mutableListOf<Macro>()
|
val macrosContent = encodeMacros(key)
|
||||||
for (macro in macroManager.getMacros()) {
|
|
||||||
val iv = getIv(macro.id)
|
|
||||||
macros.add(
|
|
||||||
macro.copy(
|
|
||||||
name = macro.name.aesCBCEncrypt(key, iv).encodeBase64String(),
|
|
||||||
macro = macro.macro.aesCBCEncrypt(key, iv).encodeBase64String()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val macrosContent = ohMyJson.encodeToString(macros)
|
|
||||||
if (log.isDebugEnabled) {
|
if (log.isDebugEnabled) {
|
||||||
log.debug("Push macros: {}", macrosContent)
|
log.debug("Push macros: {}", macrosContent)
|
||||||
}
|
}
|
||||||
@@ -370,22 +113,11 @@ abstract class GitSyncer : Syncer {
|
|||||||
|
|
||||||
// Keymap
|
// Keymap
|
||||||
if (config.ranges.contains(SyncRange.Keymap)) {
|
if (config.ranges.contains(SyncRange.Keymap)) {
|
||||||
val keymaps = mutableListOf<JsonObject>()
|
val keymapsContent = encodeKeymaps()
|
||||||
for (keymap in keymapManager.getKeymaps()) {
|
if (log.isDebugEnabled) {
|
||||||
// 只读的是内置的
|
log.debug("Push keymaps: {}", keymapsContent)
|
||||||
if (keymap.isReadonly) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
keymaps.add(keymap.toJSONObject())
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keymaps.isNotEmpty()) {
|
|
||||||
val keymapsContent = ohMyJson.encodeToString(keymaps)
|
|
||||||
if (log.isDebugEnabled) {
|
|
||||||
log.debug("Push keymaps: {}", keymapsContent)
|
|
||||||
}
|
|
||||||
gistFiles.add(GistFile("Keymaps", keymapsContent))
|
|
||||||
}
|
}
|
||||||
|
gistFiles.add(GistFile("Keymaps", keymapsContent))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gistFiles.isEmpty()) {
|
if (gistFiles.isEmpty()) {
|
||||||
|
|||||||
299
src/main/kotlin/app/termora/sync/SafetySyncer.kt
Normal file
299
src/main/kotlin/app/termora/sync/SafetySyncer.kt
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
package app.termora.sync
|
||||||
|
|
||||||
|
import app.termora.*
|
||||||
|
import app.termora.AES.CBC.aesCBCDecrypt
|
||||||
|
import app.termora.AES.CBC.aesCBCEncrypt
|
||||||
|
import app.termora.AES.decodeBase64
|
||||||
|
import app.termora.AES.encodeBase64String
|
||||||
|
import app.termora.Application.ohMyJson
|
||||||
|
import app.termora.highlight.KeywordHighlight
|
||||||
|
import app.termora.highlight.KeywordHighlightManager
|
||||||
|
import app.termora.keymap.Keymap
|
||||||
|
import app.termora.keymap.KeymapManager
|
||||||
|
import app.termora.keymgr.KeyManager
|
||||||
|
import app.termora.keymgr.OhKeyPair
|
||||||
|
import app.termora.macro.Macro
|
||||||
|
import app.termora.macro.MacroManager
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
import org.apache.commons.lang3.ArrayUtils
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import javax.swing.SwingUtilities
|
||||||
|
|
||||||
|
abstract class SafetySyncer : Syncer {
|
||||||
|
companion object {
|
||||||
|
private val log = LoggerFactory.getLogger(SafetySyncer::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected val description = "${Application.getName()} config"
|
||||||
|
protected val httpClient get() = Application.httpClient
|
||||||
|
protected val hostManager get() = HostManager.getInstance()
|
||||||
|
protected val keyManager get() = KeyManager.getInstance()
|
||||||
|
protected val keywordHighlightManager get() = KeywordHighlightManager.getInstance()
|
||||||
|
protected val macroManager get() = MacroManager.getInstance()
|
||||||
|
protected val keymapManager get() = KeymapManager.getInstance()
|
||||||
|
|
||||||
|
protected fun decodeHosts(text: String, config: SyncConfig) {
|
||||||
|
// aes key
|
||||||
|
val key = getKey(config)
|
||||||
|
val encryptedHosts = ohMyJson.decodeFromString<List<EncryptedHost>>(text)
|
||||||
|
val hosts = hostManager.hosts().associateBy { it.id }
|
||||||
|
|
||||||
|
for (encryptedHost in encryptedHosts) {
|
||||||
|
val oldHost = hosts[encryptedHost.id]
|
||||||
|
|
||||||
|
// 如果一样,则无需配置
|
||||||
|
if (oldHost != null) {
|
||||||
|
if (oldHost.updateDate == encryptedHost.updateDate) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// aes iv
|
||||||
|
val iv = getIv(encryptedHost.id)
|
||||||
|
val host = Host(
|
||||||
|
id = encryptedHost.id,
|
||||||
|
name = encryptedHost.name.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
||||||
|
protocol = Protocol.valueOf(
|
||||||
|
encryptedHost.protocol.decodeBase64().aesCBCDecrypt(key, iv).decodeToString()
|
||||||
|
),
|
||||||
|
host = encryptedHost.host.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
||||||
|
port = encryptedHost.port.decodeBase64().aesCBCDecrypt(key, iv)
|
||||||
|
.decodeToString().toIntOrNull() ?: 0,
|
||||||
|
username = encryptedHost.username.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
||||||
|
remark = encryptedHost.remark.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
||||||
|
authentication = ohMyJson.decodeFromString(
|
||||||
|
encryptedHost.authentication.decodeBase64().aesCBCDecrypt(key, iv).decodeToString()
|
||||||
|
),
|
||||||
|
proxy = ohMyJson.decodeFromString(
|
||||||
|
encryptedHost.proxy.decodeBase64().aesCBCDecrypt(key, iv).decodeToString()
|
||||||
|
),
|
||||||
|
options = ohMyJson.decodeFromString(
|
||||||
|
encryptedHost.options.decodeBase64().aesCBCDecrypt(key, iv).decodeToString()
|
||||||
|
),
|
||||||
|
tunnelings = ohMyJson.decodeFromString(
|
||||||
|
encryptedHost.tunnelings.decodeBase64().aesCBCDecrypt(key, iv).decodeToString()
|
||||||
|
),
|
||||||
|
sort = encryptedHost.sort,
|
||||||
|
parentId = encryptedHost.parentId.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
||||||
|
ownerId = encryptedHost.ownerId.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
||||||
|
creatorId = encryptedHost.creatorId.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
||||||
|
createDate = encryptedHost.createDate,
|
||||||
|
updateDate = encryptedHost.updateDate,
|
||||||
|
deleted = encryptedHost.deleted
|
||||||
|
)
|
||||||
|
SwingUtilities.invokeLater { hostManager.addHost(host) }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (log.isWarnEnabled) {
|
||||||
|
log.warn("Decode host: ${encryptedHost.id} failed. error: {}", e.message, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (log.isDebugEnabled) {
|
||||||
|
log.debug("Decode hosts: {}", text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun encodeHosts(key: ByteArray): String {
|
||||||
|
val encryptedHosts = mutableListOf<EncryptedHost>()
|
||||||
|
for (host in hostManager.hosts()) {
|
||||||
|
// aes iv
|
||||||
|
val iv = ArrayUtils.subarray(host.id.padEnd(16, '0').toByteArray(), 0, 16)
|
||||||
|
val encryptedHost = EncryptedHost()
|
||||||
|
encryptedHost.id = host.id
|
||||||
|
encryptedHost.name = host.name.aesCBCEncrypt(key, iv).encodeBase64String()
|
||||||
|
encryptedHost.protocol = host.protocol.name.aesCBCEncrypt(key, iv).encodeBase64String()
|
||||||
|
encryptedHost.host = host.host.aesCBCEncrypt(key, iv).encodeBase64String()
|
||||||
|
encryptedHost.port = "${host.port}".aesCBCEncrypt(key, iv).encodeBase64String()
|
||||||
|
encryptedHost.username = host.username.aesCBCEncrypt(key, iv).encodeBase64String()
|
||||||
|
encryptedHost.remark = host.remark.aesCBCEncrypt(key, iv).encodeBase64String()
|
||||||
|
encryptedHost.authentication = ohMyJson.encodeToString(host.authentication)
|
||||||
|
.aesCBCEncrypt(key, iv).encodeBase64String()
|
||||||
|
encryptedHost.proxy = ohMyJson.encodeToString(host.proxy).aesCBCEncrypt(key, iv).encodeBase64String()
|
||||||
|
encryptedHost.options =
|
||||||
|
ohMyJson.encodeToString(host.options).aesCBCEncrypt(key, iv).encodeBase64String()
|
||||||
|
encryptedHost.tunnelings =
|
||||||
|
ohMyJson.encodeToString(host.tunnelings).aesCBCEncrypt(key, iv).encodeBase64String()
|
||||||
|
encryptedHost.sort = host.sort
|
||||||
|
encryptedHost.deleted = host.deleted
|
||||||
|
encryptedHost.parentId = host.parentId.aesCBCEncrypt(key, iv).encodeBase64String()
|
||||||
|
encryptedHost.ownerId = host.ownerId.aesCBCEncrypt(key, iv).encodeBase64String()
|
||||||
|
encryptedHost.creatorId = host.creatorId.aesCBCEncrypt(key, iv).encodeBase64String()
|
||||||
|
encryptedHost.createDate = host.createDate
|
||||||
|
encryptedHost.updateDate = host.updateDate
|
||||||
|
encryptedHosts.add(encryptedHost)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ohMyJson.encodeToString(encryptedHosts)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun decodeKeys(text: String, config: SyncConfig) {
|
||||||
|
// aes key
|
||||||
|
val key = getKey(config)
|
||||||
|
val encryptedKeys = ohMyJson.decodeFromString<List<OhKeyPair>>(text)
|
||||||
|
|
||||||
|
for (encryptedKey in encryptedKeys) {
|
||||||
|
try {
|
||||||
|
// aes iv
|
||||||
|
val iv = getIv(encryptedKey.id)
|
||||||
|
val keyPair = OhKeyPair(
|
||||||
|
id = encryptedKey.id,
|
||||||
|
publicKey = encryptedKey.publicKey.decodeBase64().aesCBCDecrypt(key, iv).encodeBase64String(),
|
||||||
|
privateKey = encryptedKey.privateKey.decodeBase64().aesCBCDecrypt(key, iv).encodeBase64String(),
|
||||||
|
type = encryptedKey.type.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
||||||
|
name = encryptedKey.name.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
||||||
|
remark = encryptedKey.remark.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
||||||
|
length = encryptedKey.length,
|
||||||
|
sort = encryptedKey.sort
|
||||||
|
)
|
||||||
|
SwingUtilities.invokeLater { keyManager.addOhKeyPair(keyPair) }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (log.isWarnEnabled) {
|
||||||
|
log.warn("Decode key: ${encryptedKey.id} failed. error: {}", e.message, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (log.isDebugEnabled) {
|
||||||
|
log.debug("Decode keys: {}", text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun encodeKeys(key: ByteArray): String {
|
||||||
|
val encryptedKeys = mutableListOf<OhKeyPair>()
|
||||||
|
for (keyPair in keyManager.getOhKeyPairs()) {
|
||||||
|
// aes iv
|
||||||
|
val iv = ArrayUtils.subarray(keyPair.id.padEnd(16, '0').toByteArray(), 0, 16)
|
||||||
|
val encryptedKeyPair = OhKeyPair(
|
||||||
|
id = keyPair.id,
|
||||||
|
publicKey = keyPair.publicKey.decodeBase64().aesCBCEncrypt(key, iv).encodeBase64String(),
|
||||||
|
privateKey = keyPair.privateKey.decodeBase64().aesCBCEncrypt(key, iv).encodeBase64String(),
|
||||||
|
type = keyPair.type.aesCBCEncrypt(key, iv).encodeBase64String(),
|
||||||
|
name = keyPair.name.aesCBCEncrypt(key, iv).encodeBase64String(),
|
||||||
|
remark = keyPair.remark.aesCBCEncrypt(key, iv).encodeBase64String(),
|
||||||
|
length = keyPair.length,
|
||||||
|
sort = keyPair.sort
|
||||||
|
)
|
||||||
|
encryptedKeys.add(encryptedKeyPair)
|
||||||
|
}
|
||||||
|
return ohMyJson.encodeToString(encryptedKeys)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun decodeKeywordHighlights(text: String, config: SyncConfig) {
|
||||||
|
// aes key
|
||||||
|
val key = getKey(config)
|
||||||
|
val encryptedKeywordHighlights = ohMyJson.decodeFromString<List<KeywordHighlight>>(text)
|
||||||
|
|
||||||
|
for (e in encryptedKeywordHighlights) {
|
||||||
|
try {
|
||||||
|
// aes iv
|
||||||
|
val iv = getIv(e.id)
|
||||||
|
keywordHighlightManager.addKeywordHighlight(
|
||||||
|
e.copy(
|
||||||
|
keyword = e.keyword.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
||||||
|
description = e.description.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
if (log.isWarnEnabled) {
|
||||||
|
log.warn("Decode KeywordHighlight: ${e.id} failed. error: {}", ex.message, ex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (log.isDebugEnabled) {
|
||||||
|
log.debug("Decode KeywordHighlight: {}", text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun encodeKeywordHighlights(key: ByteArray): String {
|
||||||
|
val keywordHighlights = mutableListOf<KeywordHighlight>()
|
||||||
|
for (keywordHighlight in keywordHighlightManager.getKeywordHighlights()) {
|
||||||
|
// aes iv
|
||||||
|
val iv = getIv(keywordHighlight.id)
|
||||||
|
val encryptedKeyPair = keywordHighlight.copy(
|
||||||
|
keyword = keywordHighlight.keyword.aesCBCEncrypt(key, iv).encodeBase64String(),
|
||||||
|
description = keywordHighlight.description.aesCBCEncrypt(key, iv).encodeBase64String(),
|
||||||
|
)
|
||||||
|
keywordHighlights.add(encryptedKeyPair)
|
||||||
|
}
|
||||||
|
return ohMyJson.encodeToString(keywordHighlights)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun decodeMacros(text: String, config: SyncConfig) {
|
||||||
|
// aes key
|
||||||
|
val key = getKey(config)
|
||||||
|
val encryptedMacros = ohMyJson.decodeFromString<List<Macro>>(text)
|
||||||
|
|
||||||
|
for (e in encryptedMacros) {
|
||||||
|
try {
|
||||||
|
// aes iv
|
||||||
|
val iv = getIv(e.id)
|
||||||
|
macroManager.addMacro(
|
||||||
|
e.copy(
|
||||||
|
name = e.name.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
||||||
|
macro = e.macro.decodeBase64().aesCBCDecrypt(key, iv).decodeToString(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
if (log.isWarnEnabled) {
|
||||||
|
log.warn("Decode Macro: ${e.id} failed. error: {}", ex.message, ex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (log.isDebugEnabled) {
|
||||||
|
log.debug("Decode Macros: {}", text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun encodeMacros(key: ByteArray): String {
|
||||||
|
val macros = mutableListOf<Macro>()
|
||||||
|
for (macro in macroManager.getMacros()) {
|
||||||
|
val iv = getIv(macro.id)
|
||||||
|
macros.add(
|
||||||
|
macro.copy(
|
||||||
|
name = macro.name.aesCBCEncrypt(key, iv).encodeBase64String(),
|
||||||
|
macro = macro.macro.aesCBCEncrypt(key, iv).encodeBase64String()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return ohMyJson.encodeToString(macros)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun decodeKeymaps(text: String, config: SyncConfig) {
|
||||||
|
|
||||||
|
for (keymap in ohMyJson.decodeFromString<List<JsonObject>>(text).mapNotNull { Keymap.fromJSON(it) }) {
|
||||||
|
keymapManager.addKeymap(keymap)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (log.isDebugEnabled) {
|
||||||
|
log.debug("Decode Keymaps: {}", text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun encodeKeymaps(): String {
|
||||||
|
val keymaps = mutableListOf<JsonObject>()
|
||||||
|
for (keymap in keymapManager.getKeymaps()) {
|
||||||
|
// 只读的是内置的
|
||||||
|
if (keymap.isReadonly) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
keymaps.add(keymap.toJSONObject())
|
||||||
|
}
|
||||||
|
|
||||||
|
return ohMyJson.encodeToString(keymaps)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun getKey(config: SyncConfig): ByteArray {
|
||||||
|
return ArrayUtils.subarray(config.token.padEnd(16, '0').toByteArray(), 0, 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun getIv(id: String): ByteArray {
|
||||||
|
return ArrayUtils.subarray(id.padEnd(16, '0').toByteArray(), 0, 16)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ enum class SyncType {
|
|||||||
GitLab,
|
GitLab,
|
||||||
GitHub,
|
GitHub,
|
||||||
Gitee,
|
Gitee,
|
||||||
|
WebDAV,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class SyncRange {
|
enum class SyncRange {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class SyncerProvider private constructor() {
|
|||||||
SyncType.GitHub -> GitHubSyncer.getInstance()
|
SyncType.GitHub -> GitHubSyncer.getInstance()
|
||||||
SyncType.Gitee -> GiteeSyncer.getInstance()
|
SyncType.Gitee -> GiteeSyncer.getInstance()
|
||||||
SyncType.GitLab -> GitLabSyncer.getInstance()
|
SyncType.GitLab -> GitLabSyncer.getInstance()
|
||||||
|
SyncType.WebDAV -> WebDAVSyncer.getInstance()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
152
src/main/kotlin/app/termora/sync/WebDAVSyncer.kt
Normal file
152
src/main/kotlin/app/termora/sync/WebDAVSyncer.kt
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
package app.termora.sync
|
||||||
|
|
||||||
|
import app.termora.Application.ohMyJson
|
||||||
|
import app.termora.ApplicationScope
|
||||||
|
import app.termora.PBKDF2
|
||||||
|
import app.termora.ResponseException
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
import kotlinx.serialization.json.put
|
||||||
|
import okhttp3.Credentials
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
class WebDAVSyncer private constructor() : SafetySyncer() {
|
||||||
|
companion object {
|
||||||
|
private val log = LoggerFactory.getLogger(WebDAVSyncer::class.java)
|
||||||
|
|
||||||
|
fun getInstance(): WebDAVSyncer {
|
||||||
|
return ApplicationScope.forApplicationScope().getOrCreate(WebDAVSyncer::class) { WebDAVSyncer() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun pull(config: SyncConfig): GistResponse {
|
||||||
|
val response = httpClient.newCall(newRequestBuilder(config).get().build()).execute()
|
||||||
|
if (!response.isSuccessful) {
|
||||||
|
throw ResponseException(response.code, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
val text = response.use { resp -> resp.body?.use { it.string() } }
|
||||||
|
?: throw ResponseException(response.code, response)
|
||||||
|
|
||||||
|
val json = ohMyJson.decodeFromString<JsonObject>(text)
|
||||||
|
|
||||||
|
// decode hosts
|
||||||
|
json["Hosts"]?.jsonPrimitive?.content?.let {
|
||||||
|
decodeHosts(it, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode KeyPairs
|
||||||
|
json["KeyPairs"]?.jsonPrimitive?.content?.let {
|
||||||
|
decodeKeys(it, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode Highlights
|
||||||
|
json["KeywordHighlights"]?.jsonPrimitive?.content?.let {
|
||||||
|
decodeKeywordHighlights(it, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode Macros
|
||||||
|
json["Macros"]?.jsonPrimitive?.content?.let {
|
||||||
|
decodeMacros(it, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode Keymaps
|
||||||
|
json["Keymaps"]?.jsonPrimitive?.content?.let {
|
||||||
|
decodeKeymaps(it, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
return GistResponse(config, emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun push(config: SyncConfig): GistResponse {
|
||||||
|
// aes key
|
||||||
|
val key = getKey(config)
|
||||||
|
val json = buildJsonObject {
|
||||||
|
// Hosts
|
||||||
|
if (config.ranges.contains(SyncRange.Hosts)) {
|
||||||
|
val hostsContent = encodeHosts(key)
|
||||||
|
if (log.isDebugEnabled) {
|
||||||
|
log.debug("Push encryptedHosts: {}", hostsContent)
|
||||||
|
}
|
||||||
|
put("Hosts", hostsContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyPairs
|
||||||
|
if (config.ranges.contains(SyncRange.KeyPairs)) {
|
||||||
|
val keysContent = encodeKeys(key)
|
||||||
|
if (log.isDebugEnabled) {
|
||||||
|
log.debug("Push encryptedKeys: {}", keysContent)
|
||||||
|
}
|
||||||
|
put("KeyPairs", keysContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Highlights
|
||||||
|
if (config.ranges.contains(SyncRange.KeywordHighlights)) {
|
||||||
|
val keywordHighlightsContent = encodeKeywordHighlights(key)
|
||||||
|
if (log.isDebugEnabled) {
|
||||||
|
log.debug("Push keywordHighlights: {}", keywordHighlightsContent)
|
||||||
|
}
|
||||||
|
put("KeywordHighlights", keywordHighlightsContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Macros
|
||||||
|
if (config.ranges.contains(SyncRange.Macros)) {
|
||||||
|
val macrosContent = encodeMacros(key)
|
||||||
|
if (log.isDebugEnabled) {
|
||||||
|
log.debug("Push macros: {}", macrosContent)
|
||||||
|
}
|
||||||
|
put("Macros", macrosContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keymap
|
||||||
|
if (config.ranges.contains(SyncRange.Keymap)) {
|
||||||
|
val keymapsContent = encodeKeymaps()
|
||||||
|
if (log.isDebugEnabled) {
|
||||||
|
log.debug("Push keymaps: {}", keymapsContent)
|
||||||
|
}
|
||||||
|
put("Keymaps", keymapsContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val response = httpClient.newCall(
|
||||||
|
newRequestBuilder(config).put(
|
||||||
|
ohMyJson.encodeToString(json)
|
||||||
|
.toRequestBody("application/json".toMediaType())
|
||||||
|
).build()
|
||||||
|
).execute()
|
||||||
|
|
||||||
|
if (!response.isSuccessful) {
|
||||||
|
throw ResponseException(response.code, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
return GistResponse(
|
||||||
|
config = config,
|
||||||
|
gists = emptyList()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun getWebDavFileUrl(config: SyncConfig): String {
|
||||||
|
return config.options["domain"] ?: throw IllegalStateException("domain is not defined")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getKey(config: SyncConfig): ByteArray {
|
||||||
|
return PBKDF2.generateSecret(
|
||||||
|
config.gistId.toCharArray(),
|
||||||
|
config.token.toByteArray(),
|
||||||
|
10000, 128
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun newRequestBuilder(config: SyncConfig): Request.Builder {
|
||||||
|
return Request.Builder()
|
||||||
|
.header("Authorization", Credentials.basic(config.gistId, config.token, Charsets.UTF_8))
|
||||||
|
.url(getWebDavFileUrl(config))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -83,6 +83,7 @@ termora.settings.sync.last-sync-time=Last sync time
|
|||||||
termora.settings.sync.gist=Gist
|
termora.settings.sync.gist=Gist
|
||||||
termora.settings.sync.token=Token
|
termora.settings.sync.token=Token
|
||||||
termora.settings.sync.type=Type
|
termora.settings.sync.type=Type
|
||||||
|
termora.settings.sync.webdav.help=WebDAV storage address, e.g. https://yourhost/webdav/termora.json
|
||||||
|
|
||||||
termora.settings.about=About
|
termora.settings.about=About
|
||||||
termora.settings.about.author=Author
|
termora.settings.about.author=Author
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ 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=类型
|
||||||
|
termora.settings.sync.webdav.help=WebDAV 的存储地址,例如:https://yourhost/webdav/termora.json
|
||||||
|
|
||||||
termora.settings.about=关于
|
termora.settings.about=关于
|
||||||
termora.settings.about.author=作者
|
termora.settings.about.author=作者
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ 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=類型
|
||||||
|
termora.settings.sync.webdav.help=WebDAV 的儲存位址,例如:https://yourhost/webdav/termora.json
|
||||||
|
|
||||||
termora.settings.about=關於
|
termora.settings.about=關於
|
||||||
termora.settings.about.author=作者
|
termora.settings.about.author=作者
|
||||||
|
|||||||
Reference in New Issue
Block a user