mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
feat: support automatic sync (#455)
This commit is contained in:
@@ -6,6 +6,7 @@ import app.termora.keymap.Keymap
|
|||||||
import app.termora.keymgr.OhKeyPair
|
import app.termora.keymgr.OhKeyPair
|
||||||
import app.termora.macro.Macro
|
import app.termora.macro.Macro
|
||||||
import app.termora.snippet.Snippet
|
import app.termora.snippet.Snippet
|
||||||
|
import app.termora.sync.SyncManager
|
||||||
import app.termora.sync.SyncType
|
import app.termora.sync.SyncType
|
||||||
import app.termora.terminal.CursorStyle
|
import app.termora.terminal.CursorStyle
|
||||||
import jetbrains.exodus.bindings.StringBinding
|
import jetbrains.exodus.bindings.StringBinding
|
||||||
@@ -288,6 +289,18 @@ class Database private constructor(private val env: Environment) : Disposable {
|
|||||||
val k = StringBinding.stringToEntry(key)
|
val k = StringBinding.stringToEntry(key)
|
||||||
val v = StringBinding.stringToEntry(value)
|
val v = StringBinding.stringToEntry(value)
|
||||||
store.put(tx, k, v)
|
store.put(tx, k, v)
|
||||||
|
|
||||||
|
// 数据变动时触发一次同步
|
||||||
|
if (name == HOST_STORE ||
|
||||||
|
name == KEYMAP_STORE ||
|
||||||
|
name == SNIPPET_STORE ||
|
||||||
|
name == KEYWORD_HIGHLIGHT_STORE ||
|
||||||
|
name == MACRO_STORE ||
|
||||||
|
name == KEY_PAIR_STORE ||
|
||||||
|
name == DELETED_DATA_STORE
|
||||||
|
) {
|
||||||
|
SyncManager.getInstance().triggerOnChanged()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun delete(tx: Transaction, name: String, key: String) {
|
private fun delete(tx: Transaction, name: String, key: String) {
|
||||||
@@ -717,6 +730,11 @@ class Database private constructor(private val env: Environment) : Disposable {
|
|||||||
* 最后同步时间
|
* 最后同步时间
|
||||||
*/
|
*/
|
||||||
var lastSyncTime by LongPropertyDelegate(0L)
|
var lastSyncTime by LongPropertyDelegate(0L)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步策略,为空就是默认手动
|
||||||
|
*/
|
||||||
|
var policy by StringPropertyDelegate(StringUtils.EMPTY)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ class NewHostTree : SimpleTree() {
|
|||||||
|
|
||||||
val c = super.getTreeCellRendererComponent(tree, text, sel, expanded, leaf, row, hasFocus)
|
val c = super.getTreeCellRendererComponent(tree, text, sel, expanded, leaf, row, hasFocus)
|
||||||
|
|
||||||
icon = node.getIcon(sel, expanded, hasFocus)
|
icon = node.getIcon(sel, expanded, tree.hasFocus())
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -18,10 +18,7 @@ import app.termora.native.FileChooser
|
|||||||
import app.termora.sftp.SFTPTab
|
import app.termora.sftp.SFTPTab
|
||||||
import app.termora.snippet.Snippet
|
import app.termora.snippet.Snippet
|
||||||
import app.termora.snippet.SnippetManager
|
import app.termora.snippet.SnippetManager
|
||||||
import app.termora.sync.SyncConfig
|
import app.termora.sync.*
|
||||||
import app.termora.sync.SyncRange
|
|
||||||
import app.termora.sync.SyncType
|
|
||||||
import app.termora.sync.SyncerProvider
|
|
||||||
import app.termora.terminal.CursorStyle
|
import app.termora.terminal.CursorStyle
|
||||||
import app.termora.terminal.DataKey
|
import app.termora.terminal.DataKey
|
||||||
import app.termora.terminal.panel.FloatingToolbarPanel
|
import app.termora.terminal.panel.FloatingToolbarPanel
|
||||||
@@ -596,6 +593,7 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
val typeComboBox = FlatComboBox<SyncType>()
|
val typeComboBox = FlatComboBox<SyncType>()
|
||||||
val tokenTextField = OutlinePasswordField(255)
|
val tokenTextField = OutlinePasswordField(255)
|
||||||
val gistTextField = OutlineTextField(255)
|
val gistTextField = OutlineTextField(255)
|
||||||
|
val policyComboBox = JComboBox<SyncPolicy>()
|
||||||
val domainTextField = OutlineTextField(255)
|
val domainTextField = OutlineTextField(255)
|
||||||
val syncConfigButton = JButton(I18n.getString("termora.settings.sync"), Icons.download)
|
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)
|
||||||
@@ -618,9 +616,22 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun initEvents() {
|
private fun initEvents() {
|
||||||
syncConfigButton.addActionListener {
|
syncConfigButton.addActionListener(object : AbstractAction() {
|
||||||
|
override fun actionPerformed(e: ActionEvent) {
|
||||||
|
if (typeComboBox.selectedItem == SyncType.WebDAV) {
|
||||||
|
if (tokenTextField.password.isEmpty()) {
|
||||||
|
tokenTextField.outline = FlatClientProperties.OUTLINE_ERROR
|
||||||
|
tokenTextField.requestFocusInWindow()
|
||||||
|
return
|
||||||
|
} else if (gistTextField.text.isEmpty()) {
|
||||||
|
gistTextField.outline = FlatClientProperties.OUTLINE_ERROR
|
||||||
|
gistTextField.requestFocusInWindow()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
swingCoroutineScope.launch(Dispatchers.IO) { sync() }
|
swingCoroutineScope.launch(Dispatchers.IO) { sync() }
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
typeComboBox.addItemListener {
|
typeComboBox.addItemListener {
|
||||||
if (it.stateChange == ItemEvent.SELECTED) {
|
if (it.stateChange == ItemEvent.SELECTED) {
|
||||||
@@ -639,6 +650,12 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
policyComboBox.addItemListener {
|
||||||
|
if (it.stateChange == ItemEvent.SELECTED) {
|
||||||
|
sync.policy = (policyComboBox.selectedItem as SyncPolicy).name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tokenTextField.document.addDocumentListener(object : DocumentAdaptor() {
|
tokenTextField.document.addDocumentListener(object : DocumentAdaptor() {
|
||||||
override fun changedUpdate(e: DocumentEvent) {
|
override fun changedUpdate(e: DocumentEvent) {
|
||||||
sync.token = String(tokenTextField.password)
|
sync.token = String(tokenTextField.password)
|
||||||
@@ -659,6 +676,7 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
visitGistBtn.addActionListener {
|
visitGistBtn.addActionListener {
|
||||||
if (typeComboBox.selectedItem == SyncType.GitLab) {
|
if (typeComboBox.selectedItem == SyncType.GitLab) {
|
||||||
if (domainTextField.text.isNotBlank()) {
|
if (domainTextField.text.isNotBlank()) {
|
||||||
@@ -706,8 +724,14 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun sync() {
|
private suspend fun sync() {
|
||||||
|
|
||||||
|
// 如果 gist 为空说明要创建一个 gist
|
||||||
|
if (gistTextField.text.isBlank()) {
|
||||||
|
if (!pushOrPull(true)) return
|
||||||
|
} else {
|
||||||
if (!pushOrPull(false)) return
|
if (!pushOrPull(false)) return
|
||||||
if (!pushOrPull(true)) return
|
if (!pushOrPull(true)) return
|
||||||
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.Swing) {
|
withContext(Dispatchers.Swing) {
|
||||||
if (hostsCheckBox.isSelected) {
|
if (hostsCheckBox.isSelected) {
|
||||||
@@ -1118,7 +1142,7 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
|
|
||||||
// sync
|
// sync
|
||||||
val syncResult = kotlin.runCatching {
|
val syncResult = kotlin.runCatching {
|
||||||
val syncer = SyncerProvider.getInstance().getSyncer(syncConfig.type)
|
val syncer = SyncManager.getInstance()
|
||||||
if (push) {
|
if (push) {
|
||||||
syncer.push(syncConfig)
|
syncer.push(syncConfig)
|
||||||
} else {
|
} else {
|
||||||
@@ -1184,6 +1208,9 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
typeComboBox.addItem(SyncType.Gitee)
|
typeComboBox.addItem(SyncType.Gitee)
|
||||||
typeComboBox.addItem(SyncType.WebDAV)
|
typeComboBox.addItem(SyncType.WebDAV)
|
||||||
|
|
||||||
|
policyComboBox.addItem(SyncPolicy.Manual)
|
||||||
|
policyComboBox.addItem(SyncPolicy.OnChange)
|
||||||
|
|
||||||
hostsCheckBox.isFocusable = false
|
hostsCheckBox.isFocusable = false
|
||||||
snippetsCheckBox.isFocusable = false
|
snippetsCheckBox.isFocusable = false
|
||||||
keysCheckBox.isFocusable = false
|
keysCheckBox.isFocusable = false
|
||||||
@@ -1198,6 +1225,12 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
macrosCheckBox.isSelected = sync.rangeMacros
|
macrosCheckBox.isSelected = sync.rangeMacros
|
||||||
keymapCheckBox.isSelected = sync.rangeKeymap
|
keymapCheckBox.isSelected = sync.rangeKeymap
|
||||||
|
|
||||||
|
if (sync.policy == SyncPolicy.Manual.name) {
|
||||||
|
policyComboBox.selectedItem = SyncPolicy.Manual
|
||||||
|
} else if (sync.policy == SyncPolicy.OnChange.name) {
|
||||||
|
policyComboBox.selectedItem = SyncPolicy.OnChange
|
||||||
|
}
|
||||||
|
|
||||||
typeComboBox.selectedItem = sync.type
|
typeComboBox.selectedItem = sync.type
|
||||||
gistTextField.text = sync.gist
|
gistTextField.text = sync.gist
|
||||||
tokenTextField.text = sync.token
|
tokenTextField.text = sync.token
|
||||||
@@ -1245,6 +1278,23 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
policyComboBox.renderer = object : DefaultListCellRenderer() {
|
||||||
|
override fun getListCellRendererComponent(
|
||||||
|
list: JList<*>?,
|
||||||
|
value: Any?,
|
||||||
|
index: Int,
|
||||||
|
isSelected: Boolean,
|
||||||
|
cellHasFocus: Boolean
|
||||||
|
): Component {
|
||||||
|
var text = value?.toString() ?: StringUtils.EMPTY
|
||||||
|
if (value == SyncPolicy.Manual) {
|
||||||
|
text = I18n.getString("termora.settings.sync.policy.manual")
|
||||||
|
} else if (value == SyncPolicy.OnChange) {
|
||||||
|
text = I18n.getString("termora.settings.sync.policy.on-change")
|
||||||
|
}
|
||||||
|
return super.getListCellRendererComponent(list, text, index, isSelected, cellHasFocus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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")}: ${
|
||||||
@@ -1255,6 +1305,7 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
|
|
||||||
refreshButtons()
|
refreshButtons()
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getIcon(isSelected: Boolean): Icon {
|
override fun getIcon(isSelected: Boolean): Icon {
|
||||||
@@ -1272,7 +1323,7 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
private fun getCenterComponent(): JComponent {
|
private fun getCenterComponent(): JComponent {
|
||||||
val layout = FormLayout(
|
val layout = FormLayout(
|
||||||
"left:pref, $formMargin, default:grow, 30dlu",
|
"left:pref, $formMargin, default:grow, 30dlu",
|
||||||
"pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref"
|
"pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref"
|
||||||
)
|
)
|
||||||
|
|
||||||
val rangeBox = FormBuilder.create()
|
val rangeBox = FormBuilder.create()
|
||||||
@@ -1322,10 +1373,17 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
gistTextField.trailingComponent = visitGistBtn
|
gistTextField.trailingComponent = visitGistBtn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val syncPolicyBox = Box.createHorizontalBox()
|
||||||
|
syncPolicyBox.add(policyComboBox)
|
||||||
|
syncPolicyBox.add(Box.createHorizontalGlue())
|
||||||
|
syncPolicyBox.add(Box.createHorizontalGlue())
|
||||||
|
|
||||||
builder.add("${tokenText}:").xy(1, rows)
|
builder.add("${tokenText}:").xy(1, rows)
|
||||||
.add(if (isWebDAV) gistTextField else tokenTextField).xy(3, rows).apply { rows += step }
|
.add(if (isWebDAV) gistTextField else tokenTextField).xy(3, rows).apply { rows += step }
|
||||||
.add("${gistText}:").xy(1, rows)
|
.add("${gistText}:").xy(1, rows)
|
||||||
.add(if (isWebDAV) tokenTextField else gistTextField).xy(3, rows).apply { rows += step }
|
.add(if (isWebDAV) tokenTextField else gistTextField).xy(3, rows).apply { rows += step }
|
||||||
|
.add("${I18n.getString("termora.settings.sync.policy")}:").xy(1, rows)
|
||||||
|
.add(syncPolicyBox).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
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ open class SimpleTree : JXTree() {
|
|||||||
): Component {
|
): Component {
|
||||||
val node = value as SimpleTreeNode<*>
|
val node = value as SimpleTreeNode<*>
|
||||||
val c = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus)
|
val c = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus)
|
||||||
icon = node.getIcon(sel, expanded, hasFocus)
|
icon = node.getIcon(sel, expanded, tree.hasFocus())
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ class KeywordHighlightDialog(owner: Window) : DialogWrapper(owner) {
|
|||||||
for (row in rows) {
|
for (row in rows) {
|
||||||
val id = model.getKeywordHighlight(row).id
|
val id = model.getKeywordHighlight(row).id
|
||||||
keywordHighlightManager.removeKeywordHighlight(id)
|
keywordHighlightManager.removeKeywordHighlight(id)
|
||||||
model.removeRow(row)
|
model.fireTableRowsDeleted(row, row)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ enum class SyncType {
|
|||||||
WebDAV,
|
WebDAV,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class SyncPolicy {
|
||||||
|
Manual,
|
||||||
|
OnChange,
|
||||||
|
}
|
||||||
|
|
||||||
enum class SyncRange {
|
enum class SyncRange {
|
||||||
Hosts,
|
Hosts,
|
||||||
KeyPairs,
|
KeyPairs,
|
||||||
|
|||||||
173
src/main/kotlin/app/termora/sync/SyncManager.kt
Normal file
173
src/main/kotlin/app/termora/sync/SyncManager.kt
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
package app.termora.sync
|
||||||
|
|
||||||
|
import app.termora.ApplicationScope
|
||||||
|
import app.termora.Database
|
||||||
|
import app.termora.Disposable
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import kotlin.random.Random
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
@Suppress("DuplicatedCode")
|
||||||
|
class SyncManager private constructor() : Disposable {
|
||||||
|
companion object {
|
||||||
|
private val log = LoggerFactory.getLogger(SyncManager::class.java)
|
||||||
|
|
||||||
|
fun getInstance(): SyncManager {
|
||||||
|
return ApplicationScope.forApplicationScope().getOrCreate(SyncManager::class) { SyncManager() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val sync get() = Database.getDatabase().sync
|
||||||
|
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||||
|
private var job: Job? = null
|
||||||
|
private var disableTrigger = false
|
||||||
|
|
||||||
|
|
||||||
|
private fun trigger() {
|
||||||
|
trigger(getSyncConfig())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun triggerOnChanged() {
|
||||||
|
if (sync.policy == SyncPolicy.OnChange.name) {
|
||||||
|
trigger()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun trigger(config: SyncConfig) {
|
||||||
|
if (disableTrigger) return
|
||||||
|
|
||||||
|
job?.cancel()
|
||||||
|
|
||||||
|
if (log.isInfoEnabled) {
|
||||||
|
log.info("Automatic synchronisation is interrupted")
|
||||||
|
}
|
||||||
|
|
||||||
|
job = coroutineScope.launch {
|
||||||
|
|
||||||
|
// 因为会频繁调用,等待 10 - 30 秒
|
||||||
|
val seconds = Random.nextInt(10, 30)
|
||||||
|
if (log.isInfoEnabled) {
|
||||||
|
log.info("Trigger synchronisation, which will take place after {} seconds", seconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(seconds.seconds)
|
||||||
|
|
||||||
|
|
||||||
|
if (!disableTrigger) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
if (log.isInfoEnabled) {
|
||||||
|
log.info("Automatic synchronisation begin")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果已经开始,设置为 null
|
||||||
|
// 因为同步的时候会修改数据,避免被中断
|
||||||
|
job = null
|
||||||
|
|
||||||
|
sync(config)
|
||||||
|
|
||||||
|
if (log.isInfoEnabled) {
|
||||||
|
log.info("Automatic synchronisation end")
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (log.isErrorEnabled) {
|
||||||
|
log.error(e.message, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sync(config: SyncConfig): SyncResponse {
|
||||||
|
return syncImmediately(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun getSyncConfig(): SyncConfig {
|
||||||
|
val range = mutableSetOf<SyncRange>()
|
||||||
|
if (sync.rangeHosts) {
|
||||||
|
range.add(SyncRange.Hosts)
|
||||||
|
}
|
||||||
|
if (sync.rangeKeyPairs) {
|
||||||
|
range.add(SyncRange.KeyPairs)
|
||||||
|
}
|
||||||
|
if (sync.rangeKeywordHighlights) {
|
||||||
|
range.add(SyncRange.KeywordHighlights)
|
||||||
|
}
|
||||||
|
if (sync.rangeMacros) {
|
||||||
|
range.add(SyncRange.Macros)
|
||||||
|
}
|
||||||
|
if (sync.rangeKeymap) {
|
||||||
|
range.add(SyncRange.Keymap)
|
||||||
|
}
|
||||||
|
if (sync.rangeSnippets) {
|
||||||
|
range.add(SyncRange.Snippets)
|
||||||
|
}
|
||||||
|
return SyncConfig(
|
||||||
|
type = sync.type,
|
||||||
|
token = sync.token,
|
||||||
|
gistId = sync.gist,
|
||||||
|
options = mapOf("domain" to sync.domain),
|
||||||
|
ranges = range
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun syncImmediately(config: SyncConfig): SyncResponse {
|
||||||
|
synchronized(this) {
|
||||||
|
return SyncResponse(pull(config), push(config))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun pull(config: SyncConfig): GistResponse {
|
||||||
|
synchronized(this) {
|
||||||
|
disableTrigger = true
|
||||||
|
try {
|
||||||
|
return SyncerProvider.getInstance().getSyncer(config.type).pull(config)
|
||||||
|
} finally {
|
||||||
|
disableTrigger = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun push(config: SyncConfig): GistResponse {
|
||||||
|
synchronized(this) {
|
||||||
|
try {
|
||||||
|
disableTrigger = true
|
||||||
|
return SyncerProvider.getInstance().getSyncer(config.type).push(config)
|
||||||
|
} finally {
|
||||||
|
disableTrigger = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
coroutineScope.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class SyncerProvider private constructor() {
|
||||||
|
companion object {
|
||||||
|
fun getInstance(): SyncerProvider {
|
||||||
|
return ApplicationScope.forApplicationScope().getOrCreate(SyncerProvider::class) { SyncerProvider() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun getSyncer(type: SyncType): Syncer {
|
||||||
|
return when (type) {
|
||||||
|
SyncType.GitHub -> GitHubSyncer.getInstance()
|
||||||
|
SyncType.Gitee -> GiteeSyncer.getInstance()
|
||||||
|
SyncType.GitLab -> GitLabSyncer.getInstance()
|
||||||
|
SyncType.WebDAV -> WebDAVSyncer.getInstance()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class SyncResponse(val pull: GistResponse, val push: GistResponse)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package app.termora.sync
|
|
||||||
|
|
||||||
import app.termora.ApplicationScope
|
|
||||||
|
|
||||||
class SyncerProvider private constructor() {
|
|
||||||
companion object {
|
|
||||||
fun getInstance(): SyncerProvider {
|
|
||||||
return ApplicationScope.forApplicationScope().getOrCreate(SyncerProvider::class) { SyncerProvider() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun getSyncer(type: SyncType): Syncer {
|
|
||||||
return when (type) {
|
|
||||||
SyncType.GitHub -> GitHubSyncer.getInstance()
|
|
||||||
SyncType.Gitee -> GiteeSyncer.getInstance()
|
|
||||||
SyncType.GitLab -> GitLabSyncer.getInstance()
|
|
||||||
SyncType.WebDAV -> WebDAVSyncer.getInstance()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -97,6 +97,9 @@ 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.sync.webdav.help=WebDAV storage address, e.g. https://yourhost/webdav/termora.json
|
||||||
|
termora.settings.sync.policy=Sync Policy
|
||||||
|
termora.settings.sync.policy.manual=Manual
|
||||||
|
termora.settings.sync.policy.on-change=On Change
|
||||||
|
|
||||||
termora.settings.about=About
|
termora.settings.about=About
|
||||||
termora.settings.about.author=Author
|
termora.settings.about.author=Author
|
||||||
|
|||||||
@@ -99,6 +99,9 @@ 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.sync.webdav.help=WebDAV 的存储地址,例如:https://yourhost/webdav/termora.json
|
||||||
|
termora.settings.sync.policy=同步策略
|
||||||
|
termora.settings.sync.policy.manual=手动
|
||||||
|
termora.settings.sync.policy.on-change=数据变动时
|
||||||
|
|
||||||
termora.settings.about=关于
|
termora.settings.about=关于
|
||||||
termora.settings.about.author=作者
|
termora.settings.about.author=作者
|
||||||
|
|||||||
@@ -110,6 +110,9 @@ 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.sync.webdav.help=WebDAV 的儲存位址,例如:https://yourhost/webdav/termora.json
|
||||||
|
termora.settings.sync.policy=同步策略
|
||||||
|
termora.settings.sync.policy.manual=手動
|
||||||
|
termora.settings.sync.policy.on-change=資料變動時
|
||||||
|
|
||||||
termora.settings.about=關於
|
termora.settings.about=關於
|
||||||
termora.settings.about.author=作者
|
termora.settings.about.author=作者
|
||||||
|
|||||||
Reference in New Issue
Block a user