mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
feat: support tencent cos
This commit is contained in:
@@ -10,7 +10,8 @@ import java.awt.Component
|
||||
import java.awt.event.ItemEvent
|
||||
import javax.swing.*
|
||||
|
||||
class BasicProxyOption : JPanel(BorderLayout()), Option {
|
||||
class BasicProxyOption(private val proxyTypes: List<ProxyType> = listOf(ProxyType.HTTP, ProxyType.SOCKS5)) :
|
||||
JPanel(BorderLayout()), Option {
|
||||
private val formMargin = "7dlu"
|
||||
|
||||
val proxyTypeComboBox = FlatComboBox<ProxyType>()
|
||||
@@ -61,8 +62,9 @@ class BasicProxyOption : JPanel(BorderLayout()), Option {
|
||||
}
|
||||
|
||||
proxyTypeComboBox.addItem(ProxyType.No)
|
||||
proxyTypeComboBox.addItem(ProxyType.HTTP)
|
||||
proxyTypeComboBox.addItem(ProxyType.SOCKS5)
|
||||
for (type in proxyTypes) {
|
||||
proxyTypeComboBox.addItem(type)
|
||||
}
|
||||
|
||||
proxyAuthenticationTypeComboBox.addItem(AuthenticationType.No)
|
||||
proxyAuthenticationTypeComboBox.addItem(AuthenticationType.Password)
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package app.termora.protocol
|
||||
|
||||
import app.termora.Disposable
|
||||
import org.apache.commons.io.IOUtils
|
||||
import java.nio.file.FileSystem
|
||||
import java.nio.file.Path
|
||||
|
||||
open class PathHandler(val fileSystem: FileSystem, val path: Path) : Disposable {
|
||||
|
||||
override fun dispose() {
|
||||
IOUtils.closeQuietly(fileSystem)
|
||||
}
|
||||
}
|
||||
@@ -442,11 +442,6 @@ class TransferVisualWindow(tab: SSHTerminalTab, visualWindowManager: VisualWindo
|
||||
override fun getWorkdir(): Path? {
|
||||
return transportPanel.workdir
|
||||
}
|
||||
|
||||
override fun getTableModel(): TransportTableModel? {
|
||||
return transportPanel.getTableModel()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -457,7 +452,6 @@ class TransferVisualWindow(tab: SSHTerminalTab, visualWindowManager: VisualWindo
|
||||
transferManager,
|
||||
object : DefaultInternalTransferManager.WorkdirProvider {
|
||||
override fun getWorkdir() = null
|
||||
override fun getTableModel() = null
|
||||
},
|
||||
createWorkdirProvider(transportPanel)
|
||||
)
|
||||
|
||||
@@ -31,6 +31,7 @@ import kotlin.collections.ArrayDeque
|
||||
import kotlin.collections.List
|
||||
import kotlin.collections.Set
|
||||
import kotlin.collections.isNotEmpty
|
||||
import kotlin.io.path.exists
|
||||
import kotlin.io.path.name
|
||||
import kotlin.io.path.pathString
|
||||
import kotlin.math.max
|
||||
@@ -49,7 +50,6 @@ class DefaultInternalTransferManager(
|
||||
|
||||
interface WorkdirProvider {
|
||||
fun getWorkdir(): Path?
|
||||
fun getTableModel(): TransportTableModel?
|
||||
}
|
||||
|
||||
|
||||
@@ -85,19 +85,14 @@ class DefaultInternalTransferManager(
|
||||
if (paths.isEmpty()) return CompletableFuture.completedFuture(Unit)
|
||||
|
||||
val future = CompletableFuture<Unit>()
|
||||
val tableModel = when (targetWorkdir.fileSystem) {
|
||||
source.getWorkdir()?.fileSystem -> source.getTableModel()
|
||||
target.getWorkdir()?.fileSystem -> target.getTableModel()
|
||||
else -> null
|
||||
}
|
||||
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val context = AskTransferContext(TransferAction.Overwrite, false)
|
||||
for (pair in paths) {
|
||||
if (mode == TransferMode.Transfer && tableModel != null && context.applyAll.not()) {
|
||||
if (mode == TransferMode.Transfer && context.applyAll.not()) {
|
||||
val action = withContext(Dispatchers.Swing) {
|
||||
getTransferAction(context, tableModel, pair.second)
|
||||
getTransferAction(context, targetWorkdir.resolve(pair.first.name), pair.second)
|
||||
}
|
||||
if (action == null) {
|
||||
break
|
||||
@@ -143,21 +138,19 @@ class DefaultInternalTransferManager(
|
||||
|
||||
private fun getTransferAction(
|
||||
context: AskTransferContext,
|
||||
model: TransportTableModel,
|
||||
path: Path,
|
||||
source: TransportTableModel.Attributes
|
||||
): TransferAction? {
|
||||
if (context.applyAll) return context.action
|
||||
|
||||
for (i in 0 until model.rowCount) {
|
||||
val c = model.getAttributes(i)
|
||||
if (c.name != source.name) continue
|
||||
val transfer = askTransfer(source, c)
|
||||
if (path.exists()) {
|
||||
val transfer = askTransfer(source, source)
|
||||
context.action = transfer.action
|
||||
context.applyAll = transfer.applyAll
|
||||
if (transfer.option != JOptionPane.OK_OPTION) return null
|
||||
}
|
||||
|
||||
return context.action
|
||||
return TransferAction.Overwrite
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.StandardOpenOption
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import kotlin.io.path.inputStream
|
||||
import kotlin.io.path.outputStream
|
||||
|
||||
@@ -18,8 +19,12 @@ class FileTransfer(
|
||||
private lateinit var input: InputStream
|
||||
private lateinit var output: OutputStream
|
||||
|
||||
private val closed = AtomicBoolean(false)
|
||||
|
||||
override suspend fun transfer(bufferSize: Int): Long {
|
||||
|
||||
if (closed.get()) throw IllegalStateException("Transfer already closed")
|
||||
|
||||
if (::input.isInitialized.not()) {
|
||||
input = source().inputStream(StandardOpenOption.READ)
|
||||
}
|
||||
@@ -48,12 +53,14 @@ class FileTransfer(
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
if (::input.isInitialized) {
|
||||
IOUtils.closeQuietly(input)
|
||||
}
|
||||
if (closed.compareAndSet(false, true)) {
|
||||
if (::input.isInitialized) {
|
||||
IOUtils.closeQuietly(input)
|
||||
}
|
||||
|
||||
if (::output.isInitialized) {
|
||||
IOUtils.closeQuietly(output)
|
||||
if (::output.isInitialized) {
|
||||
IOUtils.closeQuietly(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -424,6 +424,11 @@ class TransferTableModel(private val coroutineScope: CoroutineScope) :
|
||||
// 异步上报,因为数据量非常大,所以采用异步
|
||||
reporter.report(node, len, System.currentTimeMillis())
|
||||
}
|
||||
|
||||
// 因为可能是异步传输,只有关闭后才能确保数据已经到达云端
|
||||
// 尤其是 S3 协议
|
||||
if (transfer is Closeable) IOUtils.closeQuietly(transfer)
|
||||
|
||||
withContext(Dispatchers.Swing) {
|
||||
if (continueTransfer(node)) {
|
||||
changeState(node, State.Done)
|
||||
|
||||
@@ -290,7 +290,7 @@ class TransportPanel(
|
||||
// 传输完成之后刷新
|
||||
transferManager.addTransferListener(object : TransferListener {
|
||||
override fun onTransferChanged(transfer: Transfer, state: TransferTreeTableNode.State) {
|
||||
if (state != TransferTreeTableNode.State.Done) return
|
||||
if (state != TransferTreeTableNode.State.Done && state != TransferTreeTableNode.State.Failed) return
|
||||
if (transfer.target().fileSystem != _fileSystem) return
|
||||
if (transfer.target() == workdir || transfer.target().parent == workdir) {
|
||||
reload(requestFocus = false)
|
||||
|
||||
@@ -95,9 +95,6 @@ class TransportViewer : JPanel(BorderLayout()), DataProvider, Disposable {
|
||||
return source.getSelectedTransportPanel()?.workdir
|
||||
}
|
||||
|
||||
override fun getTableModel(): TransportTableModel? {
|
||||
return source.getSelectedTransportPanel()?.getTableModel()
|
||||
}
|
||||
|
||||
},
|
||||
object : DefaultInternalTransferManager.WorkdirProvider {
|
||||
@@ -105,9 +102,6 @@ class TransportViewer : JPanel(BorderLayout()), DataProvider, Disposable {
|
||||
return target.getSelectedTransportPanel()?.workdir
|
||||
}
|
||||
|
||||
override fun getTableModel(): TransportTableModel? {
|
||||
return target.getSelectedTransportPanel()?.getTableModel()
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ internal class SFTPPathHandler(
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
super.dispose()
|
||||
session.removeCloseFutureListener(listener)
|
||||
IOUtils.closeQuietly(fileSystem)
|
||||
IOUtils.closeQuietly(session)
|
||||
IOUtils.closeQuietly(client)
|
||||
}
|
||||
|
||||
52
src/main/kotlin/app/termora/transfer/s3/S3FileAttributes.kt
Normal file
52
src/main/kotlin/app/termora/transfer/s3/S3FileAttributes.kt
Normal file
@@ -0,0 +1,52 @@
|
||||
package app.termora.transfer.s3
|
||||
|
||||
import java.nio.file.attribute.BasicFileAttributes
|
||||
import java.nio.file.attribute.FileTime
|
||||
|
||||
data class S3FileAttributes(
|
||||
private val lastModifiedTime: Long = 0,
|
||||
private val lastAccessTime: Long = 0,
|
||||
private val creationTime: Long = 0,
|
||||
|
||||
private val regularFile: Boolean = false,
|
||||
private val directory: Boolean = false,
|
||||
private val symbolicLink: Boolean = false,
|
||||
private val other: Boolean = false,
|
||||
private val size: Long = 0,
|
||||
) : BasicFileAttributes {
|
||||
override fun lastModifiedTime(): FileTime {
|
||||
return FileTime.fromMillis(lastModifiedTime)
|
||||
}
|
||||
|
||||
override fun lastAccessTime(): FileTime {
|
||||
return FileTime.fromMillis(lastAccessTime)
|
||||
}
|
||||
|
||||
override fun creationTime(): FileTime {
|
||||
return FileTime.fromMillis(creationTime)
|
||||
}
|
||||
|
||||
override fun isRegularFile(): Boolean {
|
||||
return regularFile
|
||||
}
|
||||
|
||||
override fun isDirectory(): Boolean {
|
||||
return directory
|
||||
}
|
||||
|
||||
override fun isSymbolicLink(): Boolean {
|
||||
return symbolicLink
|
||||
}
|
||||
|
||||
override fun isOther(): Boolean {
|
||||
return other
|
||||
}
|
||||
|
||||
override fun size(): Long {
|
||||
return size
|
||||
}
|
||||
|
||||
override fun fileKey(): Any? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
39
src/main/kotlin/app/termora/transfer/s3/S3FileSystem.kt
Normal file
39
src/main/kotlin/app/termora/transfer/s3/S3FileSystem.kt
Normal file
@@ -0,0 +1,39 @@
|
||||
package app.termora.transfer.s3
|
||||
|
||||
import org.apache.sshd.common.file.util.BaseFileSystem
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.attribute.UserPrincipalLookupService
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
open class S3FileSystem(provider: S3FileSystemProvider) : BaseFileSystem<S3Path>(provider) {
|
||||
|
||||
private val isOpen = AtomicBoolean(true)
|
||||
|
||||
override fun create(root: String?, names: List<String>): S3Path {
|
||||
val path = S3Path(this, root, names)
|
||||
if (names.isEmpty()) {
|
||||
path.attributes = path.attributes.copy(directory = true)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
override fun isOpen(): Boolean {
|
||||
return isOpen.get()
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
isOpen.compareAndSet(false, true)
|
||||
}
|
||||
|
||||
override fun getRootDirectories(): Iterable<Path> {
|
||||
return mutableSetOf<Path>(create(separator))
|
||||
}
|
||||
|
||||
override fun supportedFileAttributeViews(): Set<String> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getUserPrincipalLookupService(): UserPrincipalLookupService {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
187
src/main/kotlin/app/termora/transfer/s3/S3FileSystemProvider.kt
Normal file
187
src/main/kotlin/app/termora/transfer/s3/S3FileSystemProvider.kt
Normal file
@@ -0,0 +1,187 @@
|
||||
package app.termora.transfer.s3
|
||||
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.net.URI
|
||||
import java.nio.channels.SeekableByteChannel
|
||||
import java.nio.file.*
|
||||
import java.nio.file.attribute.*
|
||||
import java.nio.file.spi.FileSystemProvider
|
||||
import kotlin.io.path.absolutePathString
|
||||
import kotlin.io.path.name
|
||||
|
||||
abstract class S3FileSystemProvider() : FileSystemProvider() {
|
||||
|
||||
/**
|
||||
* 因为 S3 协议不存在文件夹,所以用户新建的文件夹先保存到内存中
|
||||
*/
|
||||
protected val directories = mutableMapOf<String, MutableList<S3Path>>()
|
||||
|
||||
override fun newFileSystem(
|
||||
uri: URI,
|
||||
env: Map<String, *>
|
||||
): FileSystem {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getFileSystem(uri: URI): FileSystem {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getPath(uri: URI): Path {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun newByteChannel(
|
||||
path: Path,
|
||||
options: Set<OpenOption>,
|
||||
vararg attrs: FileAttribute<*>
|
||||
): SeekableByteChannel {
|
||||
if (path !is S3Path) throw UnsupportedOperationException("path must be a S3Path")
|
||||
return if (options.contains(StandardOpenOption.WRITE)) {
|
||||
S3WriteSeekableByteChannel(getOutputStream(path))
|
||||
} else {
|
||||
S3ReadSeekableByteChannel(getInputStream(path), path.attributes.size())
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun getOutputStream(path: S3Path): OutputStream
|
||||
abstract fun getInputStream(path: S3Path): InputStream
|
||||
|
||||
override fun newDirectoryStream(
|
||||
dir: Path,
|
||||
filter: DirectoryStream.Filter<in Path>
|
||||
): DirectoryStream<Path> {
|
||||
return object : DirectoryStream<Path> {
|
||||
override fun iterator(): MutableIterator<Path> {
|
||||
return fetchChildren(dir as S3Path).iterator()
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun fetchChildren(path: S3Path): MutableList<S3Path>
|
||||
|
||||
|
||||
override fun createDirectory(dir: Path, vararg attrs: FileAttribute<*>) {
|
||||
synchronized(this) {
|
||||
if (dir !is S3Path) throw UnsupportedOperationException("dir must be a S3Path")
|
||||
if (dir.isRoot || dir.isBucket) throw UnsupportedOperationException("No operation permission")
|
||||
val parent = dir.parent ?: throw UnsupportedOperationException("No operation permission")
|
||||
directories.computeIfAbsent(parent.absolutePathString()) { mutableListOf() }
|
||||
.add(dir.apply {
|
||||
attributes = attributes.copy(directory = true, lastModifiedTime = System.currentTimeMillis())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override fun delete(path: Path) {
|
||||
if (path !is S3Path) throw UnsupportedOperationException("path must be a S3Path")
|
||||
if (path.attributes.isDirectory) {
|
||||
val parent = path.parent
|
||||
if (parent != null) {
|
||||
synchronized(this) {
|
||||
directories[parent.absolutePathString()]?.removeIf { it.name == path.name }
|
||||
}
|
||||
}
|
||||
delete(path, true)
|
||||
} else {
|
||||
delete(path, false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun checkAccess(path: Path, vararg modes: AccessMode) {
|
||||
if (path !is S3Path) throw UnsupportedOperationException("path must be a S3Path")
|
||||
checkAccess(path, *modes)
|
||||
}
|
||||
|
||||
abstract fun delete(path: S3Path, isDirectory: Boolean)
|
||||
abstract fun checkAccess(path: S3Path, vararg modes: AccessMode)
|
||||
|
||||
override fun copy(
|
||||
source: Path?,
|
||||
target: Path?,
|
||||
vararg options: CopyOption?
|
||||
) {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun move(
|
||||
source: Path?,
|
||||
target: Path?,
|
||||
vararg options: CopyOption?
|
||||
) {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun isSameFile(path: Path?, path2: Path?): Boolean {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun isHidden(path: Path?): Boolean {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun getFileStore(path: Path?): FileStore? {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun <V : FileAttributeView> getFileAttributeView(
|
||||
path: Path,
|
||||
type: Class<V>,
|
||||
vararg options: LinkOption?
|
||||
): V {
|
||||
if (path is S3Path) {
|
||||
return type.cast(object : BasicFileAttributeView {
|
||||
override fun name(): String {
|
||||
return "basic"
|
||||
}
|
||||
|
||||
override fun readAttributes(): BasicFileAttributes {
|
||||
return path.attributes
|
||||
}
|
||||
|
||||
override fun setTimes(
|
||||
lastModifiedTime: FileTime?,
|
||||
lastAccessTime: FileTime?,
|
||||
createTime: FileTime?
|
||||
) {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun <A : BasicFileAttributes> readAttributes(
|
||||
path: Path,
|
||||
type: Class<A>,
|
||||
vararg options: LinkOption
|
||||
): A {
|
||||
if (path is S3Path) {
|
||||
return type.cast(getFileAttributeView(path, BasicFileAttributeView::class.java).readAttributes())
|
||||
}
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun readAttributes(
|
||||
path: Path?,
|
||||
attributes: String?,
|
||||
vararg options: LinkOption?
|
||||
): Map<String?, Any?>? {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun setAttribute(
|
||||
path: Path?,
|
||||
attribute: String?,
|
||||
value: Any?,
|
||||
vararg options: LinkOption?
|
||||
) {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
60
src/main/kotlin/app/termora/transfer/s3/S3Path.kt
Normal file
60
src/main/kotlin/app/termora/transfer/s3/S3Path.kt
Normal file
@@ -0,0 +1,60 @@
|
||||
package app.termora.transfer.s3
|
||||
|
||||
import app.termora.transfer.WithPathAttributes
|
||||
import org.apache.sshd.common.file.util.BasePath
|
||||
import java.nio.file.LinkOption
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.absolutePathString
|
||||
|
||||
open class S3Path(
|
||||
fileSystem: S3FileSystem,
|
||||
root: String?,
|
||||
names: List<String>,
|
||||
) : BasePath<S3Path, S3FileSystem>(fileSystem, root, names), WithPathAttributes {
|
||||
|
||||
|
||||
private val separator get() = fileSystem.separator
|
||||
|
||||
var attributes = S3FileAttributes()
|
||||
|
||||
/**
|
||||
* 是否是 Bucket
|
||||
*/
|
||||
open val isBucket get() = parent != null && parent?.parent == null
|
||||
|
||||
/**
|
||||
* 是否是根
|
||||
*/
|
||||
open val isRoot get() = absolutePathString() == separator
|
||||
|
||||
/**
|
||||
* Bucket Name
|
||||
*/
|
||||
open val bucketName: String get() = names.first()
|
||||
|
||||
/**
|
||||
* 获取 Bucket
|
||||
*/
|
||||
open val bucket: S3Path get() = fileSystem.getPath(root, bucketName)
|
||||
|
||||
/**
|
||||
* 获取所在 Bucket 的路径
|
||||
*/
|
||||
open val objectName: String get() = names.subList(1, names.size).joinToString(separator)
|
||||
|
||||
override fun getCustomType(): String? {
|
||||
if (isBucket) return "Bucket"
|
||||
return null
|
||||
}
|
||||
|
||||
override fun toRealPath(vararg options: LinkOption): Path {
|
||||
return toAbsolutePath()
|
||||
}
|
||||
|
||||
override fun getParent(): S3Path? {
|
||||
val path = super.getParent() ?: return null
|
||||
path.attributes = path.attributes.copy(directory = true)
|
||||
return path
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package app.termora.transfer.s3
|
||||
|
||||
import org.apache.commons.io.IOUtils
|
||||
import java.io.InputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.channels.Channels
|
||||
import java.nio.channels.SeekableByteChannel
|
||||
|
||||
open class S3ReadSeekableByteChannel(input: InputStream, private val size: Long) : SeekableByteChannel {
|
||||
private val channel = Channels.newChannel(input)
|
||||
private var position: Long = 0
|
||||
|
||||
override fun read(dst: ByteBuffer): Int {
|
||||
val bytesRead = channel.read(dst)
|
||||
if (bytesRead > 0) {
|
||||
position += bytesRead
|
||||
}
|
||||
return bytesRead
|
||||
}
|
||||
|
||||
override fun write(src: ByteBuffer): Int {
|
||||
throw UnsupportedOperationException("Read-only channel")
|
||||
}
|
||||
|
||||
override fun position(): Long {
|
||||
return position
|
||||
}
|
||||
|
||||
override fun position(newPosition: Long): SeekableByteChannel {
|
||||
throw UnsupportedOperationException("Seek not supported in streaming read")
|
||||
}
|
||||
|
||||
override fun size(): Long {
|
||||
return size
|
||||
}
|
||||
|
||||
override fun truncate(size: Long): SeekableByteChannel {
|
||||
throw UnsupportedOperationException("Read-only channel")
|
||||
}
|
||||
|
||||
override fun isOpen(): Boolean {
|
||||
return channel.isOpen
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
IOUtils.closeQuietly(channel)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package app.termora.transfer.s3
|
||||
|
||||
import org.apache.commons.io.IOUtils
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.channels.Channels
|
||||
import java.nio.channels.SeekableByteChannel
|
||||
|
||||
open class S3WriteSeekableByteChannel(output: OutputStream) : SeekableByteChannel {
|
||||
private val channel = Channels.newChannel(output)
|
||||
|
||||
override fun read(dst: ByteBuffer): Int {
|
||||
throw UnsupportedOperationException("read not supported")
|
||||
}
|
||||
|
||||
override fun write(src: ByteBuffer): Int {
|
||||
return channel.write(src)
|
||||
}
|
||||
|
||||
override fun position(): Long {
|
||||
throw UnsupportedOperationException("position not supported")
|
||||
}
|
||||
|
||||
override fun position(newPosition: Long): SeekableByteChannel {
|
||||
throw UnsupportedOperationException("position not supported")
|
||||
}
|
||||
|
||||
override fun size(): Long {
|
||||
throw UnsupportedOperationException("size not supported")
|
||||
}
|
||||
|
||||
override fun truncate(size: Long): SeekableByteChannel {
|
||||
throw UnsupportedOperationException("truncate not supported")
|
||||
}
|
||||
|
||||
override fun isOpen(): Boolean {
|
||||
return channel.isOpen
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
IOUtils.closeQuietly(channel)
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package app.termora
|
||||
|
||||
import app.termora.database.DataEntity
|
||||
import app.termora.database.DataType
|
||||
import app.termora.database.OwnerType
|
||||
import app.termora.database.SettingEntity
|
||||
import org.jetbrains.exposed.v1.jdbc.Database
|
||||
import org.jetbrains.exposed.v1.jdbc.SchemaUtils
|
||||
import org.jetbrains.exposed.v1.jdbc.insert
|
||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||
import kotlin.test.Test
|
||||
|
||||
|
||||
class ExposedTest {
|
||||
|
||||
|
||||
@Test
|
||||
fun test() {
|
||||
val database = Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver", user = "sa")
|
||||
|
||||
transaction(database) {
|
||||
SchemaUtils.create(DataEntity, SettingEntity)
|
||||
|
||||
println(DataEntity.insert {
|
||||
it[ownerId] = "Test"
|
||||
it[ownerType] = OwnerType.User.name
|
||||
it[type] = DataType.KeywordHighlight.name
|
||||
it[data] = "hello 中文".repeat(10000)
|
||||
} get DataEntity.id)
|
||||
|
||||
println(SettingEntity.insert {
|
||||
it[name] = "Test"
|
||||
it[value] = "hello 中文".repeat(10000)
|
||||
} get SettingEntity.id)
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ class HostTest {
|
||||
"""
|
||||
{
|
||||
"name": "test",
|
||||
"protocol": SSHProtocolProvider.PROTOCOL,
|
||||
"protocol": "SSH",
|
||||
"test": ""
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package app.termora.account
|
||||
|
||||
import kotlin.test.Test
|
||||
|
||||
class ServerManagerTest {
|
||||
@Test
|
||||
fun test() {
|
||||
ServerManager.getInstance().login(
|
||||
Server(
|
||||
name = "test",
|
||||
server = "http://127.0.0.1:8080"
|
||||
), "admin", "admin"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
package app.termora.vfs2.sftp
|
||||
|
||||
import app.termora.SSHDTest
|
||||
import app.termora.randomUUID
|
||||
import org.apache.commons.vfs2.*
|
||||
import org.apache.commons.vfs2.impl.DefaultFileSystemManager
|
||||
import org.apache.commons.vfs2.provider.local.DefaultLocalFileProvider
|
||||
import org.apache.sshd.sftp.client.SftpClientFactory
|
||||
import java.io.File
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class MySftpFileProviderTest : SSHDTest() {
|
||||
|
||||
companion object {
|
||||
init {
|
||||
val fileSystemManager = DefaultFileSystemManager()
|
||||
fileSystemManager.addProvider("sftp", MySftpFileProvider.instance)
|
||||
fileSystemManager.addProvider("file", DefaultLocalFileProvider())
|
||||
fileSystemManager.init()
|
||||
VFS.setManager(fileSystemManager)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetExecutable() {
|
||||
val file = newFileObject("/config/test.txt")
|
||||
file.createFile()
|
||||
file.refresh()
|
||||
assertFalse(file.isExecutable)
|
||||
file.setExecutable(true, false)
|
||||
file.refresh()
|
||||
assertTrue(file.isExecutable)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateFile() {
|
||||
val file = newFileObject("/config/test.txt")
|
||||
assertFalse(file.exists())
|
||||
file.createFile()
|
||||
assertTrue(file.exists())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testWriteAndReadFile() {
|
||||
val file = newFileObject("/config/test.txt")
|
||||
file.createFile()
|
||||
assertFalse(file.content.isOpen)
|
||||
|
||||
val os = file.content.outputStream
|
||||
os.write("test".toByteArray())
|
||||
os.flush()
|
||||
assertTrue(file.content.isOpen)
|
||||
|
||||
os.close()
|
||||
assertFalse(file.content.isOpen)
|
||||
|
||||
val input = file.content.inputStream
|
||||
assertEquals("test", String(input.readAllBytes()))
|
||||
assertTrue(file.content.isOpen)
|
||||
input.close()
|
||||
assertFalse(file.content.isOpen)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateFolder() {
|
||||
val file = newFileObject("/config/test")
|
||||
assertFalse(file.exists())
|
||||
file.createFolder()
|
||||
assertTrue(file.exists())
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testSftpClient() {
|
||||
val session = newClientSession()
|
||||
val client = SftpClientFactory.instance().createSftpClient(session)
|
||||
assertTrue(client.isOpen)
|
||||
session.close()
|
||||
assertFalse(client.isOpen)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCopy() {
|
||||
val file = newFileObject("/config/sshd.pid")
|
||||
val filepath = File("build", randomUUID())
|
||||
val localFile = getVFS().resolveFile("file://${filepath.absolutePath}")
|
||||
|
||||
localFile.copyFrom(file, Selectors.SELECT_ALL)
|
||||
assertEquals(
|
||||
file.content.getString(Charsets.UTF_8),
|
||||
localFile.content.getString(Charsets.UTF_8)
|
||||
)
|
||||
|
||||
localFile.delete()
|
||||
}
|
||||
|
||||
private fun getVFS(): FileSystemManager {
|
||||
return VFS.getManager()
|
||||
}
|
||||
|
||||
private fun newFileObject(path: String): FileObject {
|
||||
val vfs = getVFS()
|
||||
val fileSystemOptions = FileSystemOptions()
|
||||
MySftpFileSystemConfigBuilder.getInstance()
|
||||
.setSftpFileSystem(fileSystemOptions, SftpClientFactory.instance().createSftpFileSystem(newClientSession()))
|
||||
return vfs.resolveFile("sftp://${path}", fileSystemOptions)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user