mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
feat: SFTP file editing support (#209)
This commit is contained in:
@@ -73,6 +73,9 @@ class ApplicationRunner {
|
|||||||
// 解密数据
|
// 解密数据
|
||||||
val openDoor = measureTimeMillis { openDoor() }
|
val openDoor = measureTimeMillis { openDoor() }
|
||||||
|
|
||||||
|
// clear temporary
|
||||||
|
clearTemporary()
|
||||||
|
|
||||||
// 启动主窗口
|
// 启动主窗口
|
||||||
val startMainFrame = measureTimeMillis { startMainFrame() }
|
val startMainFrame = measureTimeMillis { startMainFrame() }
|
||||||
|
|
||||||
@@ -94,6 +97,22 @@ class ApplicationRunner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("OPT_IN_USAGE")
|
||||||
|
private fun clearTemporary() {
|
||||||
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
|
||||||
|
// 启动时清除
|
||||||
|
FileUtils.cleanDirectory(File(Application.getBaseDataDir(), "temporary"))
|
||||||
|
|
||||||
|
// 关闭时清除
|
||||||
|
Disposer.register(ApplicationScope.forApplicationScope(), object : Disposable {
|
||||||
|
override fun dispose() {
|
||||||
|
FileUtils.cleanDirectory(File(Application.getBaseDataDir(), "temporary"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private fun openDoor() {
|
private fun openDoor() {
|
||||||
if (Doorman.getInstance().isWorking()) {
|
if (Doorman.getInstance().isWorking()) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import com.formdev.flatlaf.util.SystemInfo
|
|||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.swing.Swing
|
import kotlinx.coroutines.swing.Swing
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
|
import org.apache.commons.io.file.PathUtils
|
||||||
import org.apache.commons.lang3.StringUtils
|
import org.apache.commons.lang3.StringUtils
|
||||||
import org.apache.commons.lang3.SystemUtils
|
import org.apache.commons.lang3.SystemUtils
|
||||||
import org.apache.commons.lang3.exception.ExceptionUtils
|
import org.apache.commons.lang3.exception.ExceptionUtils
|
||||||
@@ -35,8 +36,11 @@ import java.nio.file.*
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.swing.*
|
import javax.swing.*
|
||||||
import javax.swing.table.DefaultTableCellRenderer
|
import javax.swing.table.DefaultTableCellRenderer
|
||||||
|
import kotlin.io.path.absolutePathString
|
||||||
import kotlin.io.path.exists
|
import kotlin.io.path.exists
|
||||||
|
import kotlin.io.path.getLastModifiedTime
|
||||||
import kotlin.io.path.isDirectory
|
import kotlin.io.path.isDirectory
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,9 +48,8 @@ import kotlin.io.path.isDirectory
|
|||||||
*/
|
*/
|
||||||
class FileSystemPanel(
|
class FileSystemPanel(
|
||||||
private val fileSystem: FileSystem,
|
private val fileSystem: FileSystem,
|
||||||
private val transportManager: TransportManager,
|
|
||||||
private val host: Host
|
private val host: Host
|
||||||
) : JPanel(BorderLayout()), Disposable, FileSystemTransportListener.Provider {
|
) : JPanel(BorderLayout()), Disposable {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val log = LoggerFactory.getLogger(FileSystemPanel::class.java)
|
private val log = LoggerFactory.getLogger(FileSystemPanel::class.java)
|
||||||
@@ -64,6 +67,12 @@ class FileSystemPanel(
|
|||||||
private val showHiddenFilesBtn = JButton(Icons.eyeClose)
|
private val showHiddenFilesBtn = JButton(Icons.eyeClose)
|
||||||
private val properties get() = Database.getDatabase().properties
|
private val properties get() = Database.getDatabase().properties
|
||||||
private val showHiddenFilesKey by lazy { "termora.transport.host.${host.id}.show-hidden-files" }
|
private val showHiddenFilesKey by lazy { "termora.transport.host.${host.id}.show-hidden-files" }
|
||||||
|
private val evt by lazy { AnActionEvent(this, StringUtils.EMPTY, EventObject(this)) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit
|
||||||
|
*/
|
||||||
|
private val coroutineScope by lazy { CoroutineScope(Dispatchers.IO + SupervisorJob()) }
|
||||||
|
|
||||||
val workdir get() = tableModel.workdir
|
val workdir get() = tableModel.workdir
|
||||||
|
|
||||||
@@ -342,6 +351,9 @@ class FileSystemPanel(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
coroutineScope.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
private fun copyLocalFileToFileSystem(files: List<File>) {
|
private fun copyLocalFileToFileSystem(files: List<File>) {
|
||||||
val event = AnActionEvent(this, StringUtils.EMPTY, EventObject(this))
|
val event = AnActionEvent(this, StringUtils.EMPTY, EventObject(this))
|
||||||
@@ -425,14 +437,6 @@ class FileSystemPanel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun addFileSystemTransportListener(listener: FileSystemTransportListener) {
|
|
||||||
listenerList.add(FileSystemTransportListener::class.java, listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun removeFileSystemTransportListener(listener: FileSystemTransportListener) {
|
|
||||||
listenerList.remove(FileSystemTransportListener::class.java, listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun openFolder() {
|
private fun openFolder() {
|
||||||
val row = table.selectedRow
|
val row = table.selectedRow
|
||||||
if (row < 0) return
|
if (row < 0) return
|
||||||
@@ -460,6 +464,7 @@ class FileSystemPanel(
|
|||||||
|
|
||||||
|
|
||||||
private fun showContextMenu(rows: IntArray, event: MouseEvent) {
|
private fun showContextMenu(rows: IntArray, event: MouseEvent) {
|
||||||
|
val paths = rows.filter { it != 0 }.map { tableModel.getCacheablePath(it) }
|
||||||
val popupMenu = FlatPopupMenu()
|
val popupMenu = FlatPopupMenu()
|
||||||
val newMenu = JMenu(I18n.getString("termora.transport.table.contextmenu.new"))
|
val newMenu = JMenu(I18n.getString("termora.transport.table.contextmenu.new"))
|
||||||
|
|
||||||
@@ -477,11 +482,22 @@ class FileSystemPanel(
|
|||||||
// 传输
|
// 传输
|
||||||
val transfer = popupMenu.add(I18n.getString("termora.transport.table.contextmenu.transfer"))
|
val transfer = popupMenu.add(I18n.getString("termora.transport.table.contextmenu.transfer"))
|
||||||
transfer.addActionListener {
|
transfer.addActionListener {
|
||||||
val paths = rows.filter { it != 0 }.map { tableModel.getCacheablePath(it) }
|
|
||||||
if (paths.isNotEmpty()) {
|
if (paths.isNotEmpty()) {
|
||||||
transport(paths)
|
transport(paths)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 编辑
|
||||||
|
val edit = popupMenu.add(I18n.getString("termora.transport.table.contextmenu.edit"))
|
||||||
|
// 不是 Linux & 不是本地文件系统 & 包含文件
|
||||||
|
edit.isEnabled = !SystemInfo.isLinux && !tableModel.isLocalFileSystem && paths.any { !it.isDirectory }
|
||||||
|
edit.addActionListener {
|
||||||
|
val files = paths.filter { !it.isDirectory }
|
||||||
|
if (files.isNotEmpty()) {
|
||||||
|
editFiles(files)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
popupMenu.addSeparator()
|
popupMenu.addSeparator()
|
||||||
|
|
||||||
// 复制路径
|
// 复制路径
|
||||||
@@ -574,6 +590,75 @@ class FileSystemPanel(
|
|||||||
popupMenu.show(table, event.x, event.y)
|
popupMenu.show(table, event.x, event.y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun editFiles(files: List<FileSystemTableModel.CacheablePath>) {
|
||||||
|
if (files.isEmpty()) return
|
||||||
|
val transportManager = evt.getData(TransportDataProviders.TransportManager) ?: return
|
||||||
|
|
||||||
|
val temporary = Paths.get(Application.getBaseDataDir().absolutePath, "temporary")
|
||||||
|
Files.createDirectories(temporary)
|
||||||
|
|
||||||
|
for (file in files) {
|
||||||
|
val dir = Files.createTempDirectory(temporary, "termora-")
|
||||||
|
val path = Paths.get(dir.absolutePathString(), file.fileName)
|
||||||
|
transportManager.addTransport(
|
||||||
|
transport = FileTransport(
|
||||||
|
name = file.fileName,
|
||||||
|
source = file.path,
|
||||||
|
target = path,
|
||||||
|
sourceHolder = this,
|
||||||
|
targetHolder = this,
|
||||||
|
listener = editFileTransportListener(file.path, path)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun editFileTransportListener(source: Path, localPath: Path): TransportListener {
|
||||||
|
return object : TransportListener {
|
||||||
|
override fun onTransportChanged(transport: Transport) {
|
||||||
|
// 传输成功
|
||||||
|
if (transport.state == TransportState.Done) {
|
||||||
|
val transportManager = evt.getData(TransportDataProviders.TransportManager) ?: return
|
||||||
|
var lastModifiedTime = localPath.getLastModifiedTime().toMillis()
|
||||||
|
|
||||||
|
if (SystemInfo.isMacOS) {
|
||||||
|
ProcessBuilder("open", "-a", "TextEdit", localPath.absolutePathString()).start()
|
||||||
|
} else if (SystemInfo.isWindows) {
|
||||||
|
ProcessBuilder("notepad", localPath.absolutePathString()).start()
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
coroutineScope.launch(Dispatchers.IO) {
|
||||||
|
while (coroutineScope.isActive) {
|
||||||
|
try {
|
||||||
|
val nowModifiedTime = localPath.getLastModifiedTime().toMillis()
|
||||||
|
if (nowModifiedTime != lastModifiedTime) {
|
||||||
|
lastModifiedTime = nowModifiedTime
|
||||||
|
// upload
|
||||||
|
transportManager.addTransport(
|
||||||
|
transport = FileTransport(
|
||||||
|
name = PathUtils.getFileNameString(localPath.fileName),
|
||||||
|
source = localPath,
|
||||||
|
target = source,
|
||||||
|
sourceHolder = this@FileSystemPanel,
|
||||||
|
targetHolder = this@FileSystemPanel,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (log.isErrorEnabled) {
|
||||||
|
log.error(e.message, e)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
delay(250.milliseconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
private fun renamePath(path: Path) {
|
private fun renamePath(path: Path) {
|
||||||
@@ -789,17 +874,31 @@ class FileSystemPanel(
|
|||||||
|
|
||||||
private suspend fun doTransport(paths: List<FileSystemTableModel.CacheablePath>) {
|
private suspend fun doTransport(paths: List<FileSystemTableModel.CacheablePath>) {
|
||||||
if (paths.isEmpty()) return
|
if (paths.isEmpty()) return
|
||||||
|
val transportPanel = evt.getData(TransportDataProviders.TransportPanel) ?: return
|
||||||
val listeners = listenerList.getListeners(FileSystemTransportListener::class.java)
|
val leftFileSystemPanel = evt.getData(TransportDataProviders.LeftFileSystemPanel) ?: return
|
||||||
if (listeners.isEmpty()) return
|
val rightFileSystemPanel = evt.getData(TransportDataProviders.RightFileSystemPanel) ?: return
|
||||||
|
val sourceFileSystemPanel = this
|
||||||
|
val targetFileSystemPanel = if (this == leftFileSystemPanel) rightFileSystemPanel else leftFileSystemPanel
|
||||||
|
|
||||||
// 收集数据
|
// 收集数据
|
||||||
for (e in paths) {
|
for (e in paths) {
|
||||||
|
|
||||||
if (!e.isDirectory) {
|
if (!e.isDirectory) {
|
||||||
|
val job = TransportJob(
|
||||||
|
fileSystemPanel = this,
|
||||||
|
workdir = workdir,
|
||||||
|
isDirectory = false,
|
||||||
|
path = e.path,
|
||||||
|
)
|
||||||
withContext(Dispatchers.Swing) {
|
withContext(Dispatchers.Swing) {
|
||||||
listeners.forEach { it.transport(this@FileSystemPanel, workdir, false, e.path) }
|
transportPanel.transport(
|
||||||
|
sourceWorkdir = workdir,
|
||||||
|
targetWorkdir = targetFileSystemPanel.workdir,
|
||||||
|
isSourceDirectory = false,
|
||||||
|
sourcePath = e.path,
|
||||||
|
sourceHolder = sourceFileSystemPanel,
|
||||||
|
targetHolder = targetFileSystemPanel
|
||||||
|
)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -811,12 +910,26 @@ class FileSystemPanel(
|
|||||||
val isDirectory = if (path.attributes != null)
|
val isDirectory = if (path.attributes != null)
|
||||||
path.attributes.isDirectory else path.isDirectory()
|
path.attributes.isDirectory else path.isDirectory()
|
||||||
withContext(Dispatchers.Swing) {
|
withContext(Dispatchers.Swing) {
|
||||||
listeners.forEach { it.transport(this@FileSystemPanel, workdir, isDirectory, path) }
|
transportPanel.transport(
|
||||||
|
sourceWorkdir = workdir,
|
||||||
|
targetWorkdir = targetFileSystemPanel.workdir,
|
||||||
|
isSourceDirectory = isDirectory,
|
||||||
|
sourcePath = path,
|
||||||
|
sourceHolder = sourceFileSystemPanel,
|
||||||
|
targetHolder = targetFileSystemPanel
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val isDirectory = path.isDirectory()
|
val isDirectory = path.isDirectory()
|
||||||
withContext(Dispatchers.Swing) {
|
withContext(Dispatchers.Swing) {
|
||||||
listeners.forEach { it.transport(this@FileSystemPanel, workdir, isDirectory, path) }
|
transportPanel.transport(
|
||||||
|
sourceWorkdir = workdir,
|
||||||
|
targetWorkdir = targetFileSystemPanel.workdir,
|
||||||
|
isSourceDirectory = isDirectory,
|
||||||
|
sourcePath = path,
|
||||||
|
sourceHolder = sourceFileSystemPanel,
|
||||||
|
targetHolder = targetFileSystemPanel
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package app.termora.transport
|
|||||||
import app.termora.*
|
import app.termora.*
|
||||||
import com.formdev.flatlaf.extras.components.FlatTabbedPane
|
import com.formdev.flatlaf.extras.components.FlatTabbedPane
|
||||||
import org.apache.commons.lang3.StringUtils
|
import org.apache.commons.lang3.StringUtils
|
||||||
|
import java.awt.Component
|
||||||
import java.awt.Point
|
import java.awt.Point
|
||||||
import java.nio.file.FileSystems
|
import java.nio.file.FileSystems
|
||||||
import java.nio.file.Path
|
|
||||||
import javax.swing.*
|
import javax.swing.*
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
@@ -13,9 +13,8 @@ import kotlin.math.max
|
|||||||
class FileSystemTabbed(
|
class FileSystemTabbed(
|
||||||
private val transportManager: TransportManager,
|
private val transportManager: TransportManager,
|
||||||
private val isLeft: Boolean = false
|
private val isLeft: Boolean = false
|
||||||
) : FlatTabbedPane(), FileSystemTransportListener.Provider, Disposable {
|
) : FlatTabbedPane(), Disposable {
|
||||||
private val addBtn = JButton(Icons.add)
|
private val addBtn = JButton(Icons.add)
|
||||||
private val listeners = mutableListOf<FileSystemTransportListener>()
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
initView()
|
initView()
|
||||||
@@ -36,23 +35,20 @@ class FileSystemTabbed(
|
|||||||
trailingComponent = toolbar
|
trailingComponent = toolbar
|
||||||
|
|
||||||
if (isLeft) {
|
if (isLeft) {
|
||||||
addFileSystemTransportProvider(
|
addTab(
|
||||||
I18n.getString("termora.transport.local"),
|
I18n.getString("termora.transport.local"), FileSystemPanel(
|
||||||
FileSystemPanel(
|
|
||||||
FileSystems.getDefault(),
|
FileSystems.getDefault(),
|
||||||
transportManager,
|
|
||||||
host = Host(
|
host = Host(
|
||||||
id = "local",
|
id = "local",
|
||||||
name = I18n.getString("termora.transport.local"),
|
name = I18n.getString("termora.transport.local"),
|
||||||
protocol = Protocol.Local,
|
protocol = Protocol.Local,
|
||||||
)
|
)
|
||||||
).apply { reload() }
|
).apply { reload() })
|
||||||
)
|
|
||||||
setTabClosable(0, false)
|
setTabClosable(0, false)
|
||||||
} else {
|
} else {
|
||||||
addFileSystemTransportProvider(
|
addTab(
|
||||||
I18n.getString("termora.transport.sftp.select-host"),
|
I18n.getString("termora.transport.sftp.select-host"),
|
||||||
SftpFileSystemPanel(transportManager)
|
SftpFileSystemPanel()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,8 +66,8 @@ class FileSystemTabbed(
|
|||||||
dialog.isVisible = true
|
dialog.isVisible = true
|
||||||
|
|
||||||
for (host in dialog.hosts) {
|
for (host in dialog.hosts) {
|
||||||
val panel = SftpFileSystemPanel(transportManager, host)
|
val panel = SftpFileSystemPanel(host)
|
||||||
addFileSystemTransportProvider(host.name, panel)
|
addTab(host.name, panel)
|
||||||
panel.connect()
|
panel.connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,9 +116,9 @@ class FileSystemTabbed(
|
|||||||
|
|
||||||
if (tabCount == 0) {
|
if (tabCount == 0) {
|
||||||
if (!isLeft) {
|
if (!isLeft) {
|
||||||
addFileSystemTransportProvider(
|
addTab(
|
||||||
I18n.getString("termora.transport.sftp.select-host"),
|
I18n.getString("termora.transport.sftp.select-host"),
|
||||||
SftpFileSystemPanel(transportManager)
|
SftpFileSystemPanel()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,39 +126,31 @@ class FileSystemTabbed(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addFileSystemTransportProvider(title: String, provider: FileSystemTransportListener.Provider) {
|
override fun addTab(title: String, component: Component) {
|
||||||
if (provider !is JComponent) {
|
super.addTab(title, component)
|
||||||
throw IllegalArgumentException("Provider is not an JComponent")
|
|
||||||
}
|
|
||||||
|
|
||||||
provider.addFileSystemTransportListener(object : FileSystemTransportListener {
|
selectedIndex = tabCount - 1
|
||||||
override fun transport(fileSystemPanel: FileSystemPanel, workdir: Path, isDirectory: Boolean, path: Path) {
|
|
||||||
listeners.forEach { it.transport(fileSystemPanel, workdir, isDirectory, path) }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 修改 Tab名称
|
if (component is SftpFileSystemPanel) {
|
||||||
provider.addPropertyChangeListener("TabName") { e ->
|
component.addPropertyChangeListener("TabName") { e ->
|
||||||
SwingUtilities.invokeLater {
|
SwingUtilities.invokeLater {
|
||||||
val name = StringUtils.defaultIfEmpty(
|
val name = StringUtils.defaultIfEmpty(
|
||||||
e.newValue.toString(),
|
e.newValue.toString(),
|
||||||
I18n.getString("termora.transport.sftp.select-host")
|
I18n.getString("termora.transport.sftp.select-host")
|
||||||
)
|
)
|
||||||
for (i in 0 until tabCount) {
|
for (i in 0 until tabCount) {
|
||||||
if (getComponentAt(i) == provider) {
|
if (getComponentAt(i) == component) {
|
||||||
setTitleAt(i, name)
|
setTitleAt(i, name)
|
||||||
break
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addTab(title, provider)
|
|
||||||
|
|
||||||
if (tabCount > 0)
|
|
||||||
selectedIndex = tabCount - 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun getSelectedFileSystemPanel(): FileSystemPanel? {
|
fun getSelectedFileSystemPanel(): FileSystemPanel? {
|
||||||
return getFileSystemPanel(selectedIndex)
|
return getFileSystemPanel(selectedIndex)
|
||||||
}
|
}
|
||||||
@@ -184,14 +172,6 @@ class FileSystemTabbed(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addFileSystemTransportListener(listener: FileSystemTransportListener) {
|
|
||||||
listeners.add(listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun removeFileSystemTransportListener(listener: FileSystemTransportListener) {
|
|
||||||
listeners.remove(listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
while (tabCount > 0) {
|
while (tabCount > 0) {
|
||||||
val c = getComponentAt(0)
|
val c = getComponentAt(0)
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
package app.termora.transport
|
|
||||||
|
|
||||||
import java.nio.file.Path
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
interface FileSystemTransportListener : EventListener {
|
|
||||||
/**
|
|
||||||
* @param workdir 当前工作目录
|
|
||||||
* @param isDirectory 要传输的是否是文件夹
|
|
||||||
* @param path 要传输的文件/文件夹
|
|
||||||
*/
|
|
||||||
fun transport(fileSystemPanel: FileSystemPanel, workdir: Path, isDirectory: Boolean, path: Path)
|
|
||||||
|
|
||||||
|
|
||||||
interface Provider {
|
|
||||||
fun addFileSystemTransportListener(listener: FileSystemTransportListener)
|
|
||||||
fun removeFileSystemTransportListener(listener: FileSystemTransportListener)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -21,15 +21,12 @@ import org.slf4j.LoggerFactory
|
|||||||
import java.awt.BorderLayout
|
import java.awt.BorderLayout
|
||||||
import java.awt.CardLayout
|
import java.awt.CardLayout
|
||||||
import java.awt.event.ActionEvent
|
import java.awt.event.ActionEvent
|
||||||
import java.nio.file.Path
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import javax.swing.*
|
import javax.swing.*
|
||||||
|
|
||||||
class SftpFileSystemPanel(
|
class SftpFileSystemPanel(
|
||||||
private val transportManager: TransportManager,
|
|
||||||
private var host: Host? = null
|
private var host: Host? = null
|
||||||
) : JPanel(BorderLayout()), Disposable,
|
) : JPanel(BorderLayout()), Disposable {
|
||||||
FileSystemTransportListener.Provider {
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val log = LoggerFactory.getLogger(SftpFileSystemPanel::class.java)
|
private val log = LoggerFactory.getLogger(SftpFileSystemPanel::class.java)
|
||||||
@@ -50,7 +47,6 @@ class SftpFileSystemPanel(
|
|||||||
private val connectingPanel = ConnectingPanel()
|
private val connectingPanel = ConnectingPanel()
|
||||||
private val selectHostPanel = SelectHostPanel()
|
private val selectHostPanel = SelectHostPanel()
|
||||||
private val connectFailedPanel = ConnectFailedPanel()
|
private val connectFailedPanel = ConnectFailedPanel()
|
||||||
private val listeners = mutableListOf<FileSystemTransportListener>()
|
|
||||||
private val isDisposed = AtomicBoolean(false)
|
private val isDisposed = AtomicBoolean(false)
|
||||||
|
|
||||||
private var client: SshClient? = null
|
private var client: SshClient? = null
|
||||||
@@ -136,17 +132,7 @@ class SftpFileSystemPanel(
|
|||||||
withContext(Dispatchers.Swing) {
|
withContext(Dispatchers.Swing) {
|
||||||
state = State.Connected
|
state = State.Connected
|
||||||
|
|
||||||
val fileSystemPanel = FileSystemPanel(fileSystem, transportManager, host)
|
val fileSystemPanel = FileSystemPanel(fileSystem, host)
|
||||||
fileSystemPanel.addFileSystemTransportListener(object : FileSystemTransportListener {
|
|
||||||
override fun transport(
|
|
||||||
fileSystemPanel: FileSystemPanel,
|
|
||||||
workdir: Path,
|
|
||||||
isDirectory: Boolean,
|
|
||||||
path: Path
|
|
||||||
) {
|
|
||||||
listeners.forEach { it.transport(fileSystemPanel, workdir, isDirectory, path) }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
cardPanel.add(fileSystemPanel, State.Connected.name)
|
cardPanel.add(fileSystemPanel, State.Connected.name)
|
||||||
cardLayout.show(cardPanel, State.Connected.name)
|
cardLayout.show(cardPanel, State.Connected.name)
|
||||||
@@ -312,11 +298,4 @@ class SftpFileSystemPanel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun addFileSystemTransportListener(listener: FileSystemTransportListener) {
|
|
||||||
listeners.add(listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun removeFileSystemTransportListener(listener: FileSystemTransportListener) {
|
|
||||||
listeners.remove(listener)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -31,10 +31,15 @@ abstract class Transport(
|
|||||||
val target: Path,
|
val target: Path,
|
||||||
val sourceHolder: Disposable,
|
val sourceHolder: Disposable,
|
||||||
val targetHolder: Disposable,
|
val targetHolder: Disposable,
|
||||||
|
val listener: TransportListener = TransportListener.EMPTY
|
||||||
) : Disposable, Runnable {
|
) : Disposable, Runnable {
|
||||||
|
|
||||||
private val listeners = ArrayList<TransportListener>()
|
private val listeners = ArrayList<TransportListener>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
listeners.add(listener)
|
||||||
|
}
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
var state = TransportState.Waiting
|
var state = TransportState.Waiting
|
||||||
protected set(value) {
|
protected set(value) {
|
||||||
@@ -142,9 +147,9 @@ private class SlidingWindowByteCounter {
|
|||||||
*/
|
*/
|
||||||
class FileTransport(
|
class FileTransport(
|
||||||
name: String, source: Path, target: Path,
|
name: String, source: Path, target: Path,
|
||||||
sourceHolder: Disposable, targetHolder: Disposable,
|
sourceHolder: Disposable, targetHolder: Disposable, listener: TransportListener = TransportListener.EMPTY
|
||||||
) : Transport(
|
) : Transport(
|
||||||
name, source, target, sourceHolder, targetHolder,
|
name, source, target, sourceHolder, targetHolder, listener
|
||||||
), CopyStreamListener {
|
), CopyStreamListener {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
27
src/main/kotlin/app/termora/transport/TransportJob.kt
Normal file
27
src/main/kotlin/app/termora/transport/TransportJob.kt
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package app.termora.transport
|
||||||
|
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
data class TransportJob(
|
||||||
|
/**
|
||||||
|
* 发起方
|
||||||
|
*/
|
||||||
|
val fileSystemPanel: FileSystemPanel,
|
||||||
|
/**
|
||||||
|
* 发起方工作目录
|
||||||
|
*/
|
||||||
|
val workdir: Path,
|
||||||
|
/**
|
||||||
|
* 要传输的文件是否是文件夹
|
||||||
|
*/
|
||||||
|
val isDirectory: Boolean,
|
||||||
|
/**
|
||||||
|
* 要传输的文件/文件夹
|
||||||
|
*/
|
||||||
|
val path: Path,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听
|
||||||
|
*/
|
||||||
|
val listener: TransportListener? = null
|
||||||
|
)
|
||||||
@@ -3,18 +3,33 @@ package app.termora.transport
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
interface TransportListener : EventListener {
|
interface TransportListener : EventListener {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val EMPTY = object : TransportListener {
|
||||||
|
override fun onTransportAdded(transport: Transport) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTransportRemoved(transport: Transport) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTransportChanged(transport: Transport) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Added
|
* Added
|
||||||
*/
|
*/
|
||||||
fun onTransportAdded(transport: Transport)
|
fun onTransportAdded(transport: Transport){}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removed
|
* Removed
|
||||||
*/
|
*/
|
||||||
fun onTransportRemoved(transport: Transport)
|
fun onTransportRemoved(transport: Transport){}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 状态变化
|
* 状态变化
|
||||||
*/
|
*/
|
||||||
fun onTransportChanged(transport: Transport)
|
fun onTransportChanged(transport: Transport){}
|
||||||
}
|
}
|
||||||
@@ -107,32 +107,6 @@ class TransportPanel : JPanel(BorderLayout()), Disposable, DataProvider {
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
leftFileSystemTabbed.addFileSystemTransportListener(object : FileSystemTransportListener {
|
|
||||||
override fun transport(fileSystemPanel: FileSystemPanel, workdir: Path, isDirectory: Boolean, path: Path) {
|
|
||||||
val target = rightFileSystemTabbed.getSelectedFileSystemPanel() ?: return
|
|
||||||
transport(
|
|
||||||
fileSystemPanel.workdir, target.workdir,
|
|
||||||
isSourceDirectory = isDirectory,
|
|
||||||
sourcePath = path,
|
|
||||||
sourceHolder = fileSystemPanel,
|
|
||||||
targetHolder = target,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
rightFileSystemTabbed.addFileSystemTransportListener(object : FileSystemTransportListener {
|
|
||||||
override fun transport(fileSystemPanel: FileSystemPanel, workdir: Path, isDirectory: Boolean, path: Path) {
|
|
||||||
val target = leftFileSystemTabbed.getSelectedFileSystemPanel() ?: return
|
|
||||||
transport(
|
|
||||||
fileSystemPanel.workdir, target.workdir,
|
|
||||||
isSourceDirectory = isDirectory,
|
|
||||||
sourcePath = path,
|
|
||||||
sourceHolder = fileSystemPanel,
|
|
||||||
targetHolder = target,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun transport(
|
fun transport(
|
||||||
|
|||||||
@@ -259,6 +259,7 @@ termora.transport.table.owner=Owner
|
|||||||
|
|
||||||
# contextmenu
|
# contextmenu
|
||||||
termora.transport.table.contextmenu.transfer=Transfer
|
termora.transport.table.contextmenu.transfer=Transfer
|
||||||
|
termora.transport.table.contextmenu.edit=${termora.keymgr.edit}
|
||||||
termora.transport.table.contextmenu.copy-path=Copy Path
|
termora.transport.table.contextmenu.copy-path=Copy Path
|
||||||
termora.transport.table.contextmenu.open-in-folder=Open in {0}
|
termora.transport.table.contextmenu.open-in-folder=Open in {0}
|
||||||
termora.transport.table.contextmenu.rename=${termora.welcome.contextmenu.rename}
|
termora.transport.table.contextmenu.rename=${termora.welcome.contextmenu.rename}
|
||||||
|
|||||||
Reference in New Issue
Block a user