mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-15 18:02:58 +08:00
feat: support WebDAV
This commit is contained in:
14
plugins/webdav/build.gradle.kts
Normal file
14
plugins/webdav/build.gradle.kts
Normal file
@@ -0,0 +1,14 @@
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
}
|
||||
|
||||
project.version = "0.0.1"
|
||||
|
||||
dependencies {
|
||||
testImplementation(kotlin("test"))
|
||||
implementation("com.github.lookfirst:sardine:5.13")
|
||||
compileOnly(project(":"))
|
||||
}
|
||||
|
||||
|
||||
apply(from = "$rootDir/plugins/common.gradle.kts")
|
||||
@@ -0,0 +1,25 @@
|
||||
package app.termora.plugins.webdav
|
||||
|
||||
import app.termora.transfer.s3.S3FileSystem
|
||||
import app.termora.transfer.s3.S3Path
|
||||
import com.github.sardine.Sardine
|
||||
|
||||
class WebDAVFileSystem(
|
||||
private val sardine: Sardine, endpoint: String,
|
||||
authorization: String,
|
||||
) :
|
||||
S3FileSystem(WebDAVFileSystemProvider(sardine, endpoint, authorization)) {
|
||||
|
||||
override fun create(root: String?, names: List<String>): S3Path {
|
||||
val path = WebDAVPath(this, root, names)
|
||||
if (names.isEmpty()) {
|
||||
path.attributes = path.attributes.copy(directory = true)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
sardine.shutdown()
|
||||
super.close()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package app.termora.plugins.webdav
|
||||
|
||||
import app.termora.Application
|
||||
import app.termora.ResponseException
|
||||
import app.termora.transfer.s3.S3FileSystemProvider
|
||||
import app.termora.transfer.s3.S3Path
|
||||
import com.github.sardine.Sardine
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
import okio.BufferedSink
|
||||
import okio.source
|
||||
import org.apache.commons.io.IOUtils
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.io.PipedInputStream
|
||||
import java.io.PipedOutputStream
|
||||
import java.net.URI
|
||||
import java.nio.file.AccessMode
|
||||
import java.nio.file.NoSuchFileException
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.attribute.FileAttribute
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import kotlin.io.path.absolutePathString
|
||||
import kotlin.io.path.name
|
||||
|
||||
class WebDAVFileSystemProvider(
|
||||
private val sardine: Sardine,
|
||||
private val endpoint: String,
|
||||
private val authorization: String,
|
||||
) : S3FileSystemProvider() {
|
||||
|
||||
|
||||
override fun getScheme(): String? {
|
||||
return "webdav"
|
||||
}
|
||||
|
||||
override fun getOutputStream(path: S3Path): OutputStream {
|
||||
return createStreamer(path)
|
||||
}
|
||||
|
||||
override fun getInputStream(path: S3Path): InputStream {
|
||||
return sardine.get(getFullUrl(path))
|
||||
}
|
||||
|
||||
private fun createStreamer(path: S3Path): OutputStream {
|
||||
val pis = PipedInputStream()
|
||||
val pos = PipedOutputStream(pis)
|
||||
val exception = AtomicReference<Throwable>()
|
||||
|
||||
val thread = Thread.ofVirtual().start {
|
||||
try {
|
||||
val builder = Request.Builder()
|
||||
.url("${endpoint}${path.absolutePathString()}")
|
||||
.put(object : RequestBody() {
|
||||
override fun contentType(): MediaType? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun contentLength(): Long {
|
||||
return -1
|
||||
}
|
||||
|
||||
override fun writeTo(sink: BufferedSink) {
|
||||
pis.source().use { sink.writeAll(it) }
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
if (authorization.isNotBlank())
|
||||
builder.header("Authorization", authorization)
|
||||
|
||||
// sardine 会重试,这里使用 okhttp
|
||||
val response = Application.httpClient.newCall(builder.build()).execute()
|
||||
IOUtils.closeQuietly(response)
|
||||
if (response.isSuccessful.not()) {
|
||||
throw ResponseException(response.code, response)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
exception.set(e)
|
||||
} finally {
|
||||
IOUtils.closeQuietly(pis)
|
||||
}
|
||||
}
|
||||
|
||||
return object : OutputStream() {
|
||||
override fun write(b: Int) {
|
||||
val exception = exception.get()
|
||||
if (exception != null) throw exception
|
||||
pos.write(b)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
pos.close()
|
||||
if (thread.isAlive) thread.join()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun fetchChildren(path: S3Path): MutableList<S3Path> {
|
||||
val paths = mutableListOf<S3Path>()
|
||||
val resources = sardine.list(getFullUrl(path))
|
||||
for (i in 1 until resources.size) {
|
||||
val resource = resources[i]
|
||||
val p = path.resolve(resource.name)
|
||||
p.attributes = p.attributes.copy(
|
||||
directory = resource.isDirectory,
|
||||
regularFile = resource.isDirectory.not(),
|
||||
size = resource.contentLength,
|
||||
lastModifiedTime = resource.modified.time,
|
||||
)
|
||||
paths.add(p)
|
||||
}
|
||||
return paths
|
||||
|
||||
}
|
||||
|
||||
override fun createDirectory(dir: Path, vararg attrs: FileAttribute<*>) {
|
||||
sardine.createDirectory(getFullUrl(dir))
|
||||
}
|
||||
|
||||
override fun delete(path: S3Path, isDirectory: Boolean) {
|
||||
sardine.delete(getFullUrl(path))
|
||||
}
|
||||
|
||||
override fun checkAccess(path: S3Path, vararg modes: AccessMode) {
|
||||
try {
|
||||
if (sardine.exists(getFullUrl(path)).not()) {
|
||||
throw NoSuchFileException(path.name)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (e is NoSuchFileException) throw e
|
||||
throw NoSuchFileException(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFullUrl(path: Path): String {
|
||||
val pathname = URI(null, null, path.absolutePathString(), null).toString()
|
||||
return "${endpoint}${pathname}"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
package app.termora.plugins.webdav
|
||||
|
||||
import app.termora.*
|
||||
import app.termora.plugin.internal.BasicProxyOption
|
||||
import com.formdev.flatlaf.FlatClientProperties
|
||||
import com.formdev.flatlaf.ui.FlatTextBorder
|
||||
import com.jgoodies.forms.builder.FormBuilder
|
||||
import com.jgoodies.forms.layout.FormLayout
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.KeyboardFocusManager
|
||||
import java.awt.event.ComponentAdapter
|
||||
import java.awt.event.ComponentEvent
|
||||
import javax.swing.*
|
||||
|
||||
class WebDAVHostOptionsPane : OptionsPane() {
|
||||
private val generalOption = GeneralOption()
|
||||
private val proxyOption = BasicProxyOption(listOf(ProxyType.HTTP))
|
||||
private val sftpOption = SFTPOption()
|
||||
|
||||
init {
|
||||
addOption(generalOption)
|
||||
addOption(proxyOption)
|
||||
addOption(sftpOption)
|
||||
|
||||
}
|
||||
|
||||
fun getHost(): Host {
|
||||
val name = generalOption.nameTextField.text
|
||||
val protocol = WebDAVProtocolProvider.PROTOCOL
|
||||
val port = 0
|
||||
var authentication = Authentication.Companion.No
|
||||
var proxy = Proxy.Companion.No
|
||||
val authenticationType = AuthenticationType.Password
|
||||
|
||||
authentication = authentication.copy(
|
||||
type = authenticationType,
|
||||
password = String(generalOption.passwordTextField.password)
|
||||
)
|
||||
|
||||
|
||||
if (proxyOption.proxyTypeComboBox.selectedItem != ProxyType.No) {
|
||||
proxy = proxy.copy(
|
||||
type = proxyOption.proxyTypeComboBox.selectedItem as ProxyType,
|
||||
host = proxyOption.proxyHostTextField.text,
|
||||
username = proxyOption.proxyUsernameTextField.text,
|
||||
password = String(proxyOption.proxyPasswordTextField.password),
|
||||
port = proxyOption.proxyPortTextField.value as Int,
|
||||
authenticationType = proxyOption.proxyAuthenticationTypeComboBox.selectedItem as AuthenticationType,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
val options = Options.Default.copy(sftpDefaultDirectory = sftpOption.defaultDirectoryField.text)
|
||||
|
||||
return Host(
|
||||
name = name,
|
||||
protocol = protocol,
|
||||
port = port,
|
||||
host = generalOption.endpointTextField.text,
|
||||
username = generalOption.usernameTextField.text,
|
||||
authentication = authentication,
|
||||
proxy = proxy,
|
||||
sort = System.currentTimeMillis(),
|
||||
remark = generalOption.remarkTextArea.text,
|
||||
options = options,
|
||||
)
|
||||
}
|
||||
|
||||
fun setHost(host: Host) {
|
||||
generalOption.nameTextField.text = host.name
|
||||
generalOption.usernameTextField.text = host.username
|
||||
generalOption.remarkTextArea.text = host.remark
|
||||
generalOption.passwordTextField.text = host.authentication.password
|
||||
generalOption.endpointTextField.text = host.host
|
||||
|
||||
proxyOption.proxyTypeComboBox.selectedItem = host.proxy.type
|
||||
proxyOption.proxyHostTextField.text = host.proxy.host
|
||||
proxyOption.proxyPasswordTextField.text = host.proxy.password
|
||||
proxyOption.proxyUsernameTextField.text = host.proxy.username
|
||||
proxyOption.proxyPortTextField.value = host.proxy.port
|
||||
proxyOption.proxyAuthenticationTypeComboBox.selectedItem = host.proxy.authenticationType
|
||||
|
||||
|
||||
sftpOption.defaultDirectoryField.text = host.options.sftpDefaultDirectory
|
||||
}
|
||||
|
||||
fun validateFields(): Boolean {
|
||||
val host = getHost()
|
||||
|
||||
// general
|
||||
if (validateField(generalOption.nameTextField)) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
if (validateField(generalOption.endpointTextField)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(generalOption.usernameTextField.text) || generalOption.passwordTextField.password.isNotEmpty()) {
|
||||
if (validateField(generalOption.usernameTextField)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (validateField(generalOption.passwordTextField)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// proxy
|
||||
if (host.proxy.type != ProxyType.No) {
|
||||
if (validateField(proxyOption.proxyHostTextField)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (host.proxy.authenticationType != AuthenticationType.No) {
|
||||
if (validateField(proxyOption.proxyUsernameTextField)
|
||||
|| validateField(proxyOption.proxyPasswordTextField)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回 true 表示有错误
|
||||
*/
|
||||
private fun validateField(textField: JTextField): Boolean {
|
||||
if (textField.isEnabled && (if (textField is JPasswordField) textField.password.isEmpty() else textField.text.isBlank())) {
|
||||
setOutlineError(textField)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun setOutlineError(c: JComponent) {
|
||||
selectOptionJComponent(c)
|
||||
c.putClientProperty(FlatClientProperties.OUTLINE, FlatClientProperties.OUTLINE_ERROR)
|
||||
c.requestFocusInWindow()
|
||||
}
|
||||
|
||||
|
||||
private inner class GeneralOption : JPanel(BorderLayout()), Option {
|
||||
val nameTextField = OutlineTextField(128)
|
||||
val usernameTextField = OutlineTextField(128)
|
||||
val passwordTextField = OutlinePasswordField(256)
|
||||
val endpointTextField = OutlineTextField(256)
|
||||
val remarkTextArea = FixedLengthTextArea(512)
|
||||
|
||||
init {
|
||||
initView()
|
||||
initEvents()
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
add(getCenterComponent(), BorderLayout.CENTER)
|
||||
}
|
||||
|
||||
private fun initEvents() {
|
||||
|
||||
addComponentListener(object : ComponentAdapter() {
|
||||
override fun componentResized(e: ComponentEvent) {
|
||||
SwingUtilities.invokeLater { nameTextField.requestFocusInWindow() }
|
||||
removeComponentListener(this)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
override fun getIcon(isSelected: Boolean): Icon {
|
||||
return Icons.settings
|
||||
}
|
||||
|
||||
override fun getTitle(): String {
|
||||
return I18n.getString("termora.new-host.general")
|
||||
}
|
||||
|
||||
override fun getJComponent(): JComponent {
|
||||
return this
|
||||
}
|
||||
|
||||
private fun getCenterComponent(): JComponent {
|
||||
val layout = FormLayout(
|
||||
"left:pref, $FORM_MARGIN, default:grow, $FORM_MARGIN, pref, $FORM_MARGIN, default",
|
||||
"pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref"
|
||||
)
|
||||
remarkTextArea.setFocusTraversalKeys(
|
||||
KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager()
|
||||
.getDefaultFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS)
|
||||
)
|
||||
remarkTextArea.setFocusTraversalKeys(
|
||||
KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager()
|
||||
.getDefaultFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS)
|
||||
)
|
||||
|
||||
remarkTextArea.rows = 8
|
||||
remarkTextArea.lineWrap = true
|
||||
remarkTextArea.border = BorderFactory.createEmptyBorder(4, 4, 4, 4)
|
||||
|
||||
|
||||
var rows = 1
|
||||
val step = 2
|
||||
val panel = FormBuilder.create().layout(layout)
|
||||
.add("${I18n.getString("termora.new-host.general.name")}:").xy(1, rows)
|
||||
.add(nameTextField).xyw(3, rows, 5).apply { rows += step }
|
||||
|
||||
.add("Endpoint:").xy(1, rows)
|
||||
.add(endpointTextField).xyw(3, rows, 5).apply { rows += step }
|
||||
|
||||
.add("${I18n.getString("termora.new-host.general.username")}:").xy(1, rows)
|
||||
.add(usernameTextField).xyw(3, rows, 5).apply { rows += step }
|
||||
|
||||
.add("${I18n.getString("termora.new-host.general.password")}:").xy(1, rows)
|
||||
.add(passwordTextField).xyw(3, rows, 5).apply { rows += step }
|
||||
|
||||
.add("${I18n.getString("termora.new-host.general.remark")}:").xy(1, rows)
|
||||
.add(JScrollPane(remarkTextArea).apply { border = FlatTextBorder() })
|
||||
.xyw(3, rows, 5).apply { rows += step }
|
||||
|
||||
.build()
|
||||
|
||||
|
||||
return panel
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private inner class SFTPOption : JPanel(BorderLayout()), Option {
|
||||
val defaultDirectoryField = OutlineTextField(255)
|
||||
|
||||
|
||||
init {
|
||||
initView()
|
||||
initEvents()
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
add(getCenterComponent(), BorderLayout.CENTER)
|
||||
}
|
||||
|
||||
private fun initEvents() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
override fun getIcon(isSelected: Boolean): Icon {
|
||||
return Icons.folder
|
||||
}
|
||||
|
||||
override fun getTitle(): String {
|
||||
return I18n.getString("termora.transport.sftp")
|
||||
}
|
||||
|
||||
override fun getJComponent(): JComponent {
|
||||
return this
|
||||
}
|
||||
|
||||
private fun getCenterComponent(): JComponent {
|
||||
val layout = FormLayout(
|
||||
"left:pref, $FORM_MARGIN, default:grow",
|
||||
"pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref"
|
||||
)
|
||||
|
||||
var rows = 1
|
||||
val step = 2
|
||||
val panel = FormBuilder.create().layout(layout)
|
||||
.add("${I18n.getString("termora.settings.sftp.default-directory")}:").xy(1, rows)
|
||||
.add(defaultDirectoryField).xy(3, rows).apply { rows += step }
|
||||
.build()
|
||||
|
||||
|
||||
return panel
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package app.termora.plugins.webdav
|
||||
|
||||
import app.termora.transfer.s3.S3FileSystem
|
||||
import app.termora.transfer.s3.S3Path
|
||||
|
||||
class WebDAVPath(fileSystem: S3FileSystem, root: String?, names: List<String>) : S3Path(fileSystem, root, names) {
|
||||
override val isBucket: Boolean
|
||||
get() = false
|
||||
|
||||
override val bucketName: String
|
||||
get() = throw UnsupportedOperationException()
|
||||
|
||||
override val objectName: String
|
||||
get() = throw UnsupportedOperationException()
|
||||
|
||||
override fun getCustomType(): String? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package app.termora.plugins.webdav
|
||||
|
||||
import app.termora.plugin.Extension
|
||||
import app.termora.plugin.ExtensionSupport
|
||||
import app.termora.plugin.PaidPlugin
|
||||
import app.termora.protocol.ProtocolHostPanelExtension
|
||||
import app.termora.protocol.ProtocolProviderExtension
|
||||
|
||||
class WebDAVPlugin : PaidPlugin {
|
||||
private val support = ExtensionSupport()
|
||||
|
||||
init {
|
||||
support.addExtension(ProtocolProviderExtension::class.java) { WebDAVProtocolProviderExtension.Companion.instance }
|
||||
support.addExtension(ProtocolHostPanelExtension::class.java) { WebDAVProtocolHostPanelExtension.Companion.instance }
|
||||
}
|
||||
|
||||
override fun getAuthor(): String {
|
||||
return "TermoraDev"
|
||||
}
|
||||
|
||||
|
||||
override fun getName(): String {
|
||||
return "WebDAV"
|
||||
}
|
||||
|
||||
|
||||
|
||||
override fun <T : Extension> getExtensions(clazz: Class<T>): List<T> {
|
||||
return support.getExtensions(clazz)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package app.termora.plugins.webdav
|
||||
|
||||
import app.termora.Disposer
|
||||
import app.termora.Host
|
||||
import app.termora.protocol.ProtocolHostPanel
|
||||
import java.awt.BorderLayout
|
||||
|
||||
class WebDAVProtocolHostPanel : ProtocolHostPanel() {
|
||||
|
||||
private val pane = WebDAVHostOptionsPane()
|
||||
|
||||
init {
|
||||
initView()
|
||||
initEvents()
|
||||
}
|
||||
|
||||
|
||||
private fun initView() {
|
||||
add(pane, BorderLayout.CENTER)
|
||||
Disposer.register(this, pane)
|
||||
}
|
||||
|
||||
private fun initEvents() {}
|
||||
|
||||
override fun getHost(): Host {
|
||||
return pane.getHost()
|
||||
}
|
||||
|
||||
override fun setHost(host: Host) {
|
||||
pane.setHost(host)
|
||||
}
|
||||
|
||||
override fun validateFields(): Boolean {
|
||||
return pane.validateFields()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package app.termora.plugins.webdav
|
||||
|
||||
import app.termora.protocol.ProtocolHostPanel
|
||||
import app.termora.protocol.ProtocolHostPanelExtension
|
||||
import app.termora.protocol.ProtocolProvider
|
||||
|
||||
class WebDAVProtocolHostPanelExtension private constructor() : ProtocolHostPanelExtension {
|
||||
companion object {
|
||||
val instance by lazy { WebDAVProtocolHostPanelExtension() }
|
||||
}
|
||||
|
||||
override fun getProtocolProvider(): ProtocolProvider {
|
||||
return WebDAVProtocolProvider.instance
|
||||
}
|
||||
|
||||
override fun createProtocolHostPanel(): ProtocolHostPanel {
|
||||
return WebDAVProtocolHostPanel()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package app.termora.plugins.webdav
|
||||
|
||||
import app.termora.AuthenticationType
|
||||
import app.termora.DynamicIcon
|
||||
import app.termora.Icons
|
||||
import app.termora.ProxyType
|
||||
import app.termora.protocol.PathHandler
|
||||
import app.termora.protocol.PathHandlerRequest
|
||||
import app.termora.protocol.TransferProtocolProvider
|
||||
import com.github.sardine.SardineFactory
|
||||
import okhttp3.Credentials
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import java.io.IOException
|
||||
import java.net.*
|
||||
|
||||
|
||||
class WebDAVProtocolProvider private constructor() : TransferProtocolProvider {
|
||||
|
||||
companion object {
|
||||
val instance by lazy { WebDAVProtocolProvider() }
|
||||
const val PROTOCOL = "WebDAV"
|
||||
}
|
||||
|
||||
override fun getProtocol(): String {
|
||||
return PROTOCOL
|
||||
}
|
||||
|
||||
override fun getIcon(width: Int, height: Int): DynamicIcon {
|
||||
return Icons.dav
|
||||
}
|
||||
|
||||
override fun createPathHandler(requester: PathHandlerRequest): PathHandler {
|
||||
val host = requester.host
|
||||
|
||||
val sardine = if (host.authentication.type != AuthenticationType.No) {
|
||||
if (host.proxy.type != ProxyType.No) {
|
||||
SardineFactory.begin(host.username, host.authentication.password, object : ProxySelector() {
|
||||
override fun select(uri: URI): List<Proxy> {
|
||||
if (host.proxy.type == ProxyType.HTTP) {
|
||||
return listOf(Proxy(Proxy.Type.HTTP, InetSocketAddress(host.proxy.host, host.proxy.port)))
|
||||
}
|
||||
return listOf(Proxy(Proxy.Type.SOCKS, InetSocketAddress(host.proxy.host, host.proxy.port)))
|
||||
}
|
||||
|
||||
override fun connectFailed(
|
||||
uri: URI,
|
||||
sa: SocketAddress,
|
||||
ioe: IOException
|
||||
) {
|
||||
throw ioe
|
||||
}
|
||||
})
|
||||
} else {
|
||||
SardineFactory.begin(host.username, host.authentication.password)
|
||||
}
|
||||
} else {
|
||||
SardineFactory.begin()
|
||||
}
|
||||
|
||||
val authorization = if (host.authentication.type != AuthenticationType.No)
|
||||
Credentials.basic(host.username, host.authentication.password) else StringUtils.EMPTY
|
||||
val defaultPath = host.options.sftpDefaultDirectory
|
||||
val fs = WebDAVFileSystem(sardine, StringUtils.removeEnd(host.host, "/"), authorization)
|
||||
return PathHandler(fs, fs.getPath(defaultPath))
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package app.termora.plugins.webdav
|
||||
|
||||
import app.termora.protocol.ProtocolProvider
|
||||
import app.termora.protocol.ProtocolProviderExtension
|
||||
|
||||
class WebDAVProtocolProviderExtension private constructor() : ProtocolProviderExtension {
|
||||
companion object {
|
||||
val instance by lazy { WebDAVProtocolProviderExtension() }
|
||||
}
|
||||
|
||||
override fun getProtocolProvider(): ProtocolProvider {
|
||||
return WebDAVProtocolProvider.instance
|
||||
}
|
||||
}
|
||||
24
plugins/webdav/src/main/resources/META-INF/plugin.xml
Normal file
24
plugins/webdav/src/main/resources/META-INF/plugin.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<termora-plugin>
|
||||
|
||||
<id>webdav</id>
|
||||
|
||||
<name>WebDAV</name>
|
||||
|
||||
<paid/>
|
||||
|
||||
<version>${projectVersion}</version>
|
||||
|
||||
<termora-version since=">=${rootProjectVersion}" until=""/>
|
||||
|
||||
<entry>app.termora.plugins.webdav.WebDAVPlugin</entry>
|
||||
|
||||
<descriptions>
|
||||
<description>Connecting to WebDAV</description>
|
||||
<description language="zh_CN">支持连接到 WebDAV</description>
|
||||
<description language="zh_TW">支援連接到 WebDAV</description>
|
||||
</descriptions>
|
||||
|
||||
<vendor url="https://github.com/TermoraDev">TermoraDev</vendor>
|
||||
|
||||
|
||||
</termora-plugin>
|
||||
@@ -0,0 +1 @@
|
||||
<svg t="1750996462119" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1582" width="16" height="16"><path d="M933.875 828.40625H591.1015625v-52.734375h290.0390625V142.859375H628.38476563l-105.46875001 105.46875H142.859375v527.34375h290.0390625v52.734375H90.125V195.59375h410.95898438l105.46874999-105.46875H933.875z m-158.203125 52.734375h52.734375v52.734375h-52.734375z m105.46875 0h52.734375v52.734375h-52.734375zM90.125 881.140625h52.734375v52.734375H90.125z m105.46875 0h52.734375v52.734375h-52.734375zM301.0625 881.140625h421.875v52.734375H301.0625z" p-id="1583" fill="#6C707E"></path><path d="M485.6328125 775.671875h52.734375v158.203125h-52.734375zM116.4921875 670.203125h791.015625v52.734375H116.4921875z" p-id="1584" fill="#6C707E"></path></svg>
|
||||
|
After Width: | Height: | Size: 798 B |
@@ -0,0 +1 @@
|
||||
<svg t="1750996462119" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1582" width="16" height="16"><path d="M933.875 828.40625H591.1015625v-52.734375h290.0390625V142.859375H628.38476563l-105.46875001 105.46875H142.859375v527.34375h290.0390625v52.734375H90.125V195.59375h410.95898438l105.46874999-105.46875H933.875z m-158.203125 52.734375h52.734375v52.734375h-52.734375z m105.46875 0h52.734375v52.734375h-52.734375zM90.125 881.140625h52.734375v52.734375H90.125z m105.46875 0h52.734375v52.734375h-52.734375zM301.0625 881.140625h421.875v52.734375H301.0625z" p-id="1583" fill="#CED0D6"></path><path d="M485.6328125 775.671875h52.734375v158.203125h-52.734375zM116.4921875 670.203125h791.015625v52.734375H116.4921875z" p-id="1584" fill="#CED0D6"></path></svg>
|
||||
|
After Width: | Height: | Size: 798 B |
Reference in New Issue
Block a user