diff --git a/plugins/cos/src/main/kotlin/app/termora/plugins/cos/COSFileSystemProvider.kt b/plugins/cos/src/main/kotlin/app/termora/plugins/cos/COSFileSystemProvider.kt index fbffe1f..9e58dde 100644 --- a/plugins/cos/src/main/kotlin/app/termora/plugins/cos/COSFileSystemProvider.kt +++ b/plugins/cos/src/main/kotlin/app/termora/plugins/cos/COSFileSystemProvider.kt @@ -21,7 +21,7 @@ class COSFileSystemProvider(private val clientHandler: COSClientHandler) : S3Fil override fun getScheme(): String? { - return "s3" + return "cos" } override fun getOutputStream(path: S3Path): OutputStream { diff --git a/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSClientHandler.kt b/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSClientHandler.kt new file mode 100644 index 0000000..6ff8062 --- /dev/null +++ b/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSClientHandler.kt @@ -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 +) : 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() + 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() + } + } + } + + +} \ No newline at end of file diff --git a/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSFileProvider.kt b/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSFileProvider.kt deleted file mode 100644 index 2e1fe4d..0000000 --- a/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSFileProvider.kt +++ /dev/null @@ -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 { - return OSSFileProvider.capabilities - } - - override fun doCreateFileSystem( - rootFileName: FileName, - fileSystemOptions: FileSystemOptions - ): FileSystem? { - TODO("Not yet implemented") - } - - -} \ No newline at end of file diff --git a/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSFileSystem.kt b/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSFileSystem.kt new file mode 100644 index 0000000..a517f6c --- /dev/null +++ b/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSFileSystem.kt @@ -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() + } +} diff --git a/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSFileSystemProvider.kt b/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSFileSystemProvider.kt new file mode 100644 index 0000000..322a90a --- /dev/null +++ b/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSFileSystemProvider.kt @@ -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() + + 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 { + val paths = mutableListOf() + + // 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) + } + } + +} \ No newline at end of file diff --git a/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSHostOptionsPane.kt b/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSHostOptionsPane.kt new file mode 100644 index 0000000..897de7e --- /dev/null +++ b/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSHostOptionsPane.kt @@ -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 + } + } + + +} \ No newline at end of file diff --git a/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSPlugin.kt b/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSPlugin.kt index 9396303..ef28ed1 100644 --- a/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSPlugin.kt +++ b/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSPlugin.kt @@ -1,8 +1,5 @@ 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.ExtensionSupport import app.termora.plugin.PaidPlugin @@ -27,6 +24,7 @@ class OSSPlugin : PaidPlugin { } + override fun getExtensions(clazz: Class): List { return support.getExtensions(clazz) } diff --git a/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSProtocolHostPanel.kt b/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSProtocolHostPanel.kt index 2fb5de8..b00c4e9 100644 --- a/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSProtocolHostPanel.kt +++ b/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSProtocolHostPanel.kt @@ -1,22 +1,36 @@ package app.termora.plugins.oss +import app.termora.Disposer import app.termora.Host import app.termora.protocol.ProtocolHostPanel -import org.apache.commons.lang3.StringUtils +import java.awt.BorderLayout 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 { - return Host( - name = StringUtils.EMPTY, - protocol = OSSProtocolProvider.PROTOCOL - ) + return pane.getHost() } override fun setHost(host: Host) { - + pane.setHost(host) } override fun validateFields(): Boolean { - return true + return pane.validateFields() } } \ No newline at end of file diff --git a/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSProtocolProvider.kt b/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSProtocolProvider.kt index 2e9e932..1b9cfa6 100644 --- a/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSProtocolProvider.kt +++ b/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSProtocolProvider.kt @@ -2,10 +2,13 @@ package app.termora.plugins.oss import app.termora.DynamicIcon import app.termora.Icons -import app.termora.protocol.FileObjectHandler -import app.termora.protocol.FileObjectRequest +import app.termora.protocol.PathHandler +import app.termora.protocol.PathHandlerRequest 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 { @@ -22,12 +25,36 @@ class OSSProtocolProvider private constructor() : TransferProtocolProvider { return Icons.aliyun } - override fun getFileProvider(): FileProvider { - return OSSFileProvider.instance + override fun createPathHandler(requester: PathHandlerRequest): PathHandler { + 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 + + 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") - } } \ No newline at end of file diff --git a/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSProtocolProviderExtension.kt b/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSProtocolProviderExtension.kt index 62dfe97..f051179 100644 --- a/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSProtocolProviderExtension.kt +++ b/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSProtocolProviderExtension.kt @@ -9,6 +9,6 @@ class OSSProtocolProviderExtension private constructor() : ProtocolProviderExten } override fun getProtocolProvider(): ProtocolProvider { - return OSSProtocolProvider.Companion.instance + return OSSProtocolProvider.instance } } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 1824f2b..ecb23dc 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,7 +4,7 @@ plugins { rootProject.name = "termora" include("plugins:s3") -//include("plugins:oss") +include("plugins:oss") include("plugins:cos") //include("plugins:obs") //include("plugins:ftp") diff --git a/src/main/kotlin/app/termora/transfer/TransportPopupMenu.kt b/src/main/kotlin/app/termora/transfer/TransportPopupMenu.kt index 9e0a3a6..a600cbe 100644 --- a/src/main/kotlin/app/termora/transfer/TransportPopupMenu.kt +++ b/src/main/kotlin/app/termora/transfer/TransportPopupMenu.kt @@ -94,7 +94,7 @@ class TransportPopupMenu( renameMenu.isEnabled = hasParent.not() && files.size == 1 deleteMenu.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) { item.text = "${item.text}(${KeyEvent.getKeyText(mnemonic)})"