mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-15 18:02:58 +08:00
feat: support tencent OSS
This commit is contained in:
@@ -21,7 +21,7 @@ class COSFileSystemProvider(private val clientHandler: COSClientHandler) : S3Fil
|
|||||||
|
|
||||||
|
|
||||||
override fun getScheme(): String? {
|
override fun getScheme(): String? {
|
||||||
return "s3"
|
return "cos"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getOutputStream(path: S3Path): OutputStream {
|
override fun getOutputStream(path: S3Path): OutputStream {
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package app.termora.plugins.oss
|
||||||
|
|
||||||
|
import app.termora.AuthenticationType
|
||||||
|
import app.termora.Proxy
|
||||||
|
import app.termora.ProxyType
|
||||||
|
import com.aliyun.oss.ClientBuilderConfiguration
|
||||||
|
import com.aliyun.oss.OSS
|
||||||
|
import com.aliyun.oss.OSSClientBuilder
|
||||||
|
import com.aliyun.oss.common.auth.CredentialsProvider
|
||||||
|
import com.aliyun.oss.model.Bucket
|
||||||
|
import java.io.Closeable
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
|
class OSSClientHandler(
|
||||||
|
private val endpoint: String,
|
||||||
|
private val cred: CredentialsProvider,
|
||||||
|
private val proxy: Proxy,
|
||||||
|
val buckets: List<Bucket>
|
||||||
|
) : Closeable {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun createCOSClient(
|
||||||
|
cred: CredentialsProvider,
|
||||||
|
endpoint: String,
|
||||||
|
region: String,
|
||||||
|
proxy: Proxy
|
||||||
|
): OSS {
|
||||||
|
val configuration = ClientBuilderConfiguration()
|
||||||
|
|
||||||
|
if (proxy.type == ProxyType.HTTP) {
|
||||||
|
configuration.proxyHost = proxy.host
|
||||||
|
configuration.proxyPort = proxy.port
|
||||||
|
if (proxy.authenticationType == AuthenticationType.Password) {
|
||||||
|
configuration.proxyPassword = proxy.password
|
||||||
|
configuration.proxyUsername = proxy.username
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var newEndpoint = endpoint
|
||||||
|
if ((newEndpoint.startsWith("http://") || newEndpoint.startsWith("https://")).not()) {
|
||||||
|
newEndpoint = "https://$endpoint"
|
||||||
|
}
|
||||||
|
|
||||||
|
val builder = OSSClientBuilder.create()
|
||||||
|
.endpoint(newEndpoint)
|
||||||
|
.credentialsProvider(cred)
|
||||||
|
|
||||||
|
if (region.isNotBlank()) {
|
||||||
|
builder.region(region)
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder
|
||||||
|
.clientConfiguration(configuration)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* key: Region
|
||||||
|
* value: Client
|
||||||
|
*/
|
||||||
|
private val clients = mutableMapOf<String, OSS>()
|
||||||
|
private val closed = AtomicBoolean(false)
|
||||||
|
|
||||||
|
fun getClientForBucket(bucket: String): OSS {
|
||||||
|
if (closed.get()) throw IllegalStateException("Client already closed")
|
||||||
|
|
||||||
|
synchronized(this) {
|
||||||
|
val bucket = buckets.first { it.name == bucket }
|
||||||
|
if (clients.containsKey(bucket.location)) {
|
||||||
|
return clients.getValue(bucket.location)
|
||||||
|
}
|
||||||
|
clients[bucket.location] = createCOSClient(cred, bucket.extranetEndpoint, bucket.location, proxy)
|
||||||
|
return clients.getValue(bucket.location)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
if (closed.compareAndSet(false, true)) {
|
||||||
|
synchronized(this) {
|
||||||
|
clients.forEach { it.value.shutdown() }
|
||||||
|
clients.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package app.termora.plugins.oss
|
|
||||||
|
|
||||||
import org.apache.commons.vfs2.Capability
|
|
||||||
import org.apache.commons.vfs2.FileName
|
|
||||||
import org.apache.commons.vfs2.FileSystem
|
|
||||||
import org.apache.commons.vfs2.FileSystemOptions
|
|
||||||
import org.apache.commons.vfs2.provider.AbstractOriginatingFileProvider
|
|
||||||
|
|
||||||
class OSSFileProvider private constructor() : AbstractOriginatingFileProvider() {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val instance by lazy { OSSFileProvider() }
|
|
||||||
val capabilities = listOf(
|
|
||||||
Capability.CREATE,
|
|
||||||
Capability.DELETE,
|
|
||||||
Capability.RENAME,
|
|
||||||
Capability.GET_TYPE,
|
|
||||||
Capability.LIST_CHILDREN,
|
|
||||||
Capability.READ_CONTENT,
|
|
||||||
Capability.URI,
|
|
||||||
Capability.WRITE_CONTENT,
|
|
||||||
Capability.GET_LAST_MODIFIED,
|
|
||||||
Capability.SET_LAST_MODIFIED_FILE,
|
|
||||||
Capability.RANDOM_ACCESS_READ,
|
|
||||||
Capability.APPEND_CONTENT
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getCapabilities(): Collection<Capability> {
|
|
||||||
return OSSFileProvider.capabilities
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun doCreateFileSystem(
|
|
||||||
rootFileName: FileName,
|
|
||||||
fileSystemOptions: FileSystemOptions
|
|
||||||
): FileSystem? {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package app.termora.plugins.oss
|
||||||
|
|
||||||
|
import app.termora.transfer.s3.S3FileSystem
|
||||||
|
import org.apache.commons.io.IOUtils
|
||||||
|
|
||||||
|
/**
|
||||||
|
* key: region
|
||||||
|
*/
|
||||||
|
class OSSFileSystem(private val clientHandler: OSSClientHandler) :
|
||||||
|
S3FileSystem(OSSFileSystemProvider(clientHandler)) {
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
IOUtils.closeQuietly(clientHandler)
|
||||||
|
super.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
package app.termora.plugins.oss
|
||||||
|
|
||||||
|
import app.termora.transfer.s3.S3FileAttributes
|
||||||
|
import app.termora.transfer.s3.S3FileSystemProvider
|
||||||
|
import app.termora.transfer.s3.S3Path
|
||||||
|
import com.aliyun.oss.model.ListObjectsRequest
|
||||||
|
import com.aliyun.oss.model.ObjectMetadata
|
||||||
|
import com.aliyun.oss.model.PutObjectRequest
|
||||||
|
import org.apache.commons.io.IOUtils
|
||||||
|
import org.apache.commons.lang3.StringUtils
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.io.PipedInputStream
|
||||||
|
import java.io.PipedOutputStream
|
||||||
|
import java.nio.file.AccessMode
|
||||||
|
import java.nio.file.NoSuchFileException
|
||||||
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
import kotlin.io.path.absolutePathString
|
||||||
|
|
||||||
|
class OSSFileSystemProvider(private val clientHandler: OSSClientHandler) : S3FileSystemProvider() {
|
||||||
|
|
||||||
|
|
||||||
|
override fun getScheme(): String? {
|
||||||
|
return "oss"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getOutputStream(path: S3Path): OutputStream {
|
||||||
|
return createStreamer(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getInputStream(path: S3Path): InputStream {
|
||||||
|
val client = clientHandler.getClientForBucket(path.bucketName)
|
||||||
|
return client.getObject(path.bucketName, path.objectName).objectContent
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createStreamer(path: S3Path): OutputStream {
|
||||||
|
val pis = PipedInputStream()
|
||||||
|
val pos = PipedOutputStream(pis)
|
||||||
|
val exception = AtomicReference<Throwable>()
|
||||||
|
|
||||||
|
val thread = Thread.ofVirtual().start {
|
||||||
|
try {
|
||||||
|
val client = clientHandler.getClientForBucket(path.bucketName)
|
||||||
|
client.putObject(PutObjectRequest(path.bucketName, path.objectName, pis, ObjectMetadata()))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
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>()
|
||||||
|
|
||||||
|
// root
|
||||||
|
if (path.isRoot) {
|
||||||
|
for (bucket in clientHandler.buckets) {
|
||||||
|
val p = path.resolve(bucket.name)
|
||||||
|
p.attributes = S3FileAttributes(
|
||||||
|
directory = true,
|
||||||
|
lastModifiedTime = bucket.creationDate.toInstant().toEpochMilli()
|
||||||
|
)
|
||||||
|
paths.add(p)
|
||||||
|
}
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
|
||||||
|
var nextMarker = StringUtils.EMPTY
|
||||||
|
val maxKeys = 100
|
||||||
|
val bucketName = path.bucketName
|
||||||
|
while (true) {
|
||||||
|
val request = ListObjectsRequest()
|
||||||
|
request.bucketName = bucketName
|
||||||
|
request.maxKeys = maxKeys
|
||||||
|
request.delimiter = path.fileSystem.separator
|
||||||
|
|
||||||
|
if (path.objectName.isNotBlank()) request.prefix = path.objectName + path.fileSystem.separator
|
||||||
|
if (nextMarker.isNotBlank()) request.marker = nextMarker
|
||||||
|
|
||||||
|
val objectListing = clientHandler.getClientForBucket(bucketName).listObjects(request)
|
||||||
|
for (e in objectListing.commonPrefixes) {
|
||||||
|
val p = path.bucket.resolve(e)
|
||||||
|
p.attributes = p.attributes.copy(directory = true)
|
||||||
|
delete(p)
|
||||||
|
paths.add(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (e in objectListing.objectSummaries) {
|
||||||
|
val p = path.bucket.resolve(e.key)
|
||||||
|
p.attributes = p.attributes.copy(
|
||||||
|
regularFile = true, size = e.size,
|
||||||
|
lastModifiedTime = e.lastModified.time
|
||||||
|
)
|
||||||
|
paths.add(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (objectListing.isTruncated.not()) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
nextMarker = objectListing.nextMarker
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
paths.addAll(directories[path.absolutePathString()] ?: emptyList())
|
||||||
|
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun delete(path: S3Path, isDirectory: Boolean) {
|
||||||
|
if (isDirectory.not())
|
||||||
|
clientHandler.getClientForBucket(path.bucketName).deleteObject(path.bucketName, path.objectName)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun checkAccess(path: S3Path, vararg modes: AccessMode) {
|
||||||
|
try {
|
||||||
|
val client = clientHandler.getClientForBucket(path.bucketName)
|
||||||
|
if (client.doesObjectExist(path.bucketName, path.objectName).not()) {
|
||||||
|
throw NoSuchFileException(path.objectName)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (e is NoSuchFileException) throw e
|
||||||
|
throw NoSuchFileException(e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,344 @@
|
|||||||
|
package app.termora.plugins.oss
|
||||||
|
|
||||||
|
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 OSSHostOptionsPane : 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 = OSSProtocolProvider.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,
|
||||||
|
extras = mutableMapOf(
|
||||||
|
"oss.delimiter" to StringUtils.defaultIfBlank(generalOption.delimiterTextField.text, "/"),
|
||||||
|
// "oss.region" to generalOption.regionComboBox.selectedItem as String,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return Host(
|
||||||
|
name = name,
|
||||||
|
protocol = protocol,
|
||||||
|
port = port,
|
||||||
|
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.delimiterTextField.text = host.options.extras["oss.delimiter"] ?: "/"
|
||||||
|
// generalOption.regionComboBox.selectedItem = host.options.extras["oss.region"] ?: StringUtils.EMPTY
|
||||||
|
|
||||||
|
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.usernameTextField)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (host.authentication.type == AuthenticationType.Password) {
|
||||||
|
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 && 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(255)
|
||||||
|
val remarkTextArea = FixedLengthTextArea(512)
|
||||||
|
|
||||||
|
val delimiterTextField = OutlineTextField(128)
|
||||||
|
|
||||||
|
init {
|
||||||
|
initView()
|
||||||
|
initEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initView() {
|
||||||
|
|
||||||
|
/*regionComboBox.isEditable = true
|
||||||
|
|
||||||
|
|
||||||
|
// 亚太-中国
|
||||||
|
regionComboBox.addItem("oss-cn-hangzhou")
|
||||||
|
regionComboBox.addItem("oss-cn-shanghai")
|
||||||
|
regionComboBox.addItem("oss-cn-nanjing")
|
||||||
|
regionComboBox.addItem("oss-cn-qingdao")
|
||||||
|
regionComboBox.addItem("oss-cn-beijing")
|
||||||
|
regionComboBox.addItem("oss-cn-zhangjiakou")
|
||||||
|
regionComboBox.addItem("oss-cn-huhehaote")
|
||||||
|
regionComboBox.addItem("oss-cn-wulanchabu")
|
||||||
|
regionComboBox.addItem("oss-cn-shenzhen")
|
||||||
|
regionComboBox.addItem("oss-cn-heyuan")
|
||||||
|
regionComboBox.addItem("oss-cn-guangzhou")
|
||||||
|
regionComboBox.addItem("oss-cn-chengdu")
|
||||||
|
regionComboBox.addItem("oss-cn-hongkong")
|
||||||
|
|
||||||
|
// 亚太-其他
|
||||||
|
regionComboBox.addItem("oss-ap-northeast-1")
|
||||||
|
regionComboBox.addItem("oss-ap-northeast-2")
|
||||||
|
regionComboBox.addItem("oss-ap-southeast-1")
|
||||||
|
regionComboBox.addItem("oss-ap-southeast-3")
|
||||||
|
regionComboBox.addItem("oss-ap-southeast-5")
|
||||||
|
regionComboBox.addItem("oss-ap-southeast-6")
|
||||||
|
regionComboBox.addItem("oss-ap-southeast-7")
|
||||||
|
|
||||||
|
// 欧洲与美洲
|
||||||
|
regionComboBox.addItem("oss-eu-central-1")
|
||||||
|
regionComboBox.addItem("oss-eu-west-1")
|
||||||
|
regionComboBox.addItem("oss-us-west-1")
|
||||||
|
regionComboBox.addItem("oss-us-east-1")
|
||||||
|
regionComboBox.addItem("oss-na-south-1")
|
||||||
|
|
||||||
|
// 中东
|
||||||
|
regionComboBox.addItem("oss-me-east-1")
|
||||||
|
regionComboBox.addItem("oss-me-central-1")
|
||||||
|
|
||||||
|
endpointTextField.isEditable = false*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
delimiterTextField.text = "/"
|
||||||
|
delimiterTextField.isEditable = false
|
||||||
|
add(getCenterComponent(), BorderLayout.CENTER)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initEvents() {
|
||||||
|
|
||||||
|
addComponentListener(object : ComponentAdapter() {
|
||||||
|
override fun componentResized(e: ComponentEvent) {
|
||||||
|
SwingUtilities.invokeLater { nameTextField.requestFocusInWindow() }
|
||||||
|
removeComponentListener(this)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/*regionComboBox.addItemListener {
|
||||||
|
if (it.stateChange == ItemEvent.SELECTED) {
|
||||||
|
endpointTextField.text = "${regionComboBox.selectedItem}.aliyuncs.com"
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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("Region:").xy(1, rows)
|
||||||
|
// .add(regionComboBox).xyw(3, rows, 5).apply { rows += step }
|
||||||
|
|
||||||
|
// .add("Endpoint:").xy(1, rows)
|
||||||
|
// .add(endpointTextField).xyw(3, rows, 5).apply { rows += step }
|
||||||
|
|
||||||
|
.add("SecretId:").xy(1, rows)
|
||||||
|
.add(usernameTextField).xyw(3, rows, 5).apply { rows += step }
|
||||||
|
|
||||||
|
.add("SecretKey:").xy(1, rows)
|
||||||
|
.add(passwordTextField).xyw(3, rows, 5).apply { rows += step }
|
||||||
|
|
||||||
|
.add("Delimiter:").xy(1, rows)
|
||||||
|
.add(delimiterTextField).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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
package app.termora.plugins.oss
|
package app.termora.plugins.oss
|
||||||
|
|
||||||
import app.termora.DynamicIcon
|
|
||||||
import app.termora.I18n
|
|
||||||
import app.termora.Icons
|
|
||||||
import app.termora.plugin.Extension
|
import app.termora.plugin.Extension
|
||||||
import app.termora.plugin.ExtensionSupport
|
import app.termora.plugin.ExtensionSupport
|
||||||
import app.termora.plugin.PaidPlugin
|
import app.termora.plugin.PaidPlugin
|
||||||
@@ -27,6 +24,7 @@ class OSSPlugin : PaidPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
override fun <T : Extension> getExtensions(clazz: Class<T>): List<T> {
|
override fun <T : Extension> getExtensions(clazz: Class<T>): List<T> {
|
||||||
return support.getExtensions(clazz)
|
return support.getExtensions(clazz)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,36 @@
|
|||||||
package app.termora.plugins.oss
|
package app.termora.plugins.oss
|
||||||
|
|
||||||
|
import app.termora.Disposer
|
||||||
import app.termora.Host
|
import app.termora.Host
|
||||||
import app.termora.protocol.ProtocolHostPanel
|
import app.termora.protocol.ProtocolHostPanel
|
||||||
import org.apache.commons.lang3.StringUtils
|
import java.awt.BorderLayout
|
||||||
|
|
||||||
class OSSProtocolHostPanel : ProtocolHostPanel() {
|
class OSSProtocolHostPanel : ProtocolHostPanel() {
|
||||||
|
|
||||||
|
private val pane = OSSHostOptionsPane()
|
||||||
|
|
||||||
|
init {
|
||||||
|
initView()
|
||||||
|
initEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun initView() {
|
||||||
|
add(pane, BorderLayout.CENTER)
|
||||||
|
Disposer.register(this, pane)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initEvents() {}
|
||||||
|
|
||||||
override fun getHost(): Host {
|
override fun getHost(): Host {
|
||||||
return Host(
|
return pane.getHost()
|
||||||
name = StringUtils.EMPTY,
|
|
||||||
protocol = OSSProtocolProvider.PROTOCOL
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setHost(host: Host) {
|
override fun setHost(host: Host) {
|
||||||
|
pane.setHost(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun validateFields(): Boolean {
|
override fun validateFields(): Boolean {
|
||||||
return true
|
return pane.validateFields()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,10 +2,13 @@ package app.termora.plugins.oss
|
|||||||
|
|
||||||
import app.termora.DynamicIcon
|
import app.termora.DynamicIcon
|
||||||
import app.termora.Icons
|
import app.termora.Icons
|
||||||
import app.termora.protocol.FileObjectHandler
|
import app.termora.protocol.PathHandler
|
||||||
import app.termora.protocol.FileObjectRequest
|
import app.termora.protocol.PathHandlerRequest
|
||||||
import app.termora.protocol.TransferProtocolProvider
|
import app.termora.protocol.TransferProtocolProvider
|
||||||
import org.apache.commons.vfs2.provider.FileProvider
|
import com.aliyun.oss.common.auth.CredentialsProviderFactory
|
||||||
|
import com.aliyun.oss.model.Bucket
|
||||||
|
import org.apache.commons.lang3.StringUtils
|
||||||
|
|
||||||
|
|
||||||
class OSSProtocolProvider private constructor() : TransferProtocolProvider {
|
class OSSProtocolProvider private constructor() : TransferProtocolProvider {
|
||||||
|
|
||||||
@@ -22,12 +25,36 @@ class OSSProtocolProvider private constructor() : TransferProtocolProvider {
|
|||||||
return Icons.aliyun
|
return Icons.aliyun
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getFileProvider(): FileProvider {
|
override fun createPathHandler(requester: PathHandlerRequest): PathHandler {
|
||||||
return OSSFileProvider.instance
|
val host = requester.host
|
||||||
|
val accessKeyId = host.username
|
||||||
|
val secretAccessKey = host.authentication.password
|
||||||
|
|
||||||
|
val credential = CredentialsProviderFactory.newDefaultCredentialProvider(accessKeyId, secretAccessKey)
|
||||||
|
|
||||||
|
// 通过默认的接口获取桶列表
|
||||||
|
val oss = OSSClientHandler.createCOSClient(
|
||||||
|
credential, "oss-cn-hangzhou.aliyuncs.com",
|
||||||
|
StringUtils.EMPTY, host.proxy
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
val buckets: List<Bucket>
|
||||||
|
|
||||||
|
try {
|
||||||
|
buckets = oss.listBuckets()
|
||||||
|
if (buckets.isEmpty()) {
|
||||||
|
throw IllegalStateException("没有获取到桶信息")
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
oss.shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
val defaultPath = host.options.sftpDefaultDirectory
|
||||||
|
val fs = OSSFileSystem(OSSClientHandler(host.host, credential, host.proxy, buckets))
|
||||||
|
return PathHandler(fs, fs.getPath(defaultPath))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getRootFileObject(requester: FileObjectRequest): FileObjectHandler {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,6 @@ class OSSProtocolProviderExtension private constructor() : ProtocolProviderExten
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getProtocolProvider(): ProtocolProvider {
|
override fun getProtocolProvider(): ProtocolProvider {
|
||||||
return OSSProtocolProvider.Companion.instance
|
return OSSProtocolProvider.instance
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ plugins {
|
|||||||
rootProject.name = "termora"
|
rootProject.name = "termora"
|
||||||
|
|
||||||
include("plugins:s3")
|
include("plugins:s3")
|
||||||
//include("plugins:oss")
|
include("plugins:oss")
|
||||||
include("plugins:cos")
|
include("plugins:cos")
|
||||||
//include("plugins:obs")
|
//include("plugins:obs")
|
||||||
//include("plugins:ftp")
|
//include("plugins:ftp")
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ class TransportPopupMenu(
|
|||||||
renameMenu.isEnabled = hasParent.not() && files.size == 1
|
renameMenu.isEnabled = hasParent.not() && files.size == 1
|
||||||
deleteMenu.isEnabled = hasParent.not() && files.isNotEmpty()
|
deleteMenu.isEnabled = hasParent.not() && files.isNotEmpty()
|
||||||
rmrfMenu.isEnabled = hasParent.not() && files.isNotEmpty()
|
rmrfMenu.isEnabled = hasParent.not() && files.isNotEmpty()
|
||||||
changePermissionsMenu.isEnabled = hasParent.not() && fileSystem is SftpFileSystem && files.size == 1
|
changePermissionsMenu.isVisible = hasParent.not() && fileSystem is SftpFileSystem && files.size == 1
|
||||||
|
|
||||||
for ((item, mnemonic) in mnemonics) {
|
for ((item, mnemonic) in mnemonics) {
|
||||||
item.text = "${item.text}(${KeyEvent.getKeyText(mnemonic)})"
|
item.text = "${item.text}(${KeyEvent.getKeyText(mnemonic)})"
|
||||||
|
|||||||
Reference in New Issue
Block a user