Files
termora/src/main/kotlin/app/termora/sftp/FileSystemViewTableModel.kt

223 lines
7.9 KiB
Kotlin

package app.termora.sftp
import app.termora.I18n
import app.termora.NativeStringComparator
import app.termora.formatBytes
import app.termora.vfs2.sftp.MySftpFileObject
import com.formdev.flatlaf.util.SystemInfo
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.swing.Swing
import kotlinx.coroutines.withContext
import org.apache.commons.lang3.StringUtils
import org.apache.commons.lang3.exception.ExceptionUtils
import org.apache.commons.lang3.time.DateFormatUtils
import org.apache.commons.vfs2.FileObject
import org.apache.commons.vfs2.FileType
import org.apache.commons.vfs2.provider.local.LocalFileSystem
import org.slf4j.LoggerFactory
import java.nio.file.attribute.PosixFilePermission
import java.nio.file.attribute.PosixFilePermissions
import java.util.*
import javax.swing.Icon
import javax.swing.SwingUtilities
import javax.swing.table.DefaultTableModel
class FileSystemViewTableModel : DefaultTableModel() {
companion object {
const val COLUMN_NAME = 0
const val COLUMN_TYPE = 1
const val COLUMN_FILE_SIZE = 2
const val COLUMN_LAST_MODIFIED_TIME = 3
const val COLUMN_ATTRS = 4
const val COLUMN_OWNER = 5
private val log = LoggerFactory.getLogger(FileSystemViewTableModel::class.java)
fun fromSftpPermissions(sftpPermissions: Int): Set<PosixFilePermission> {
val result = mutableSetOf<PosixFilePermission>()
// 将十进制权限转换为八进制字符串
val octalPermissions = sftpPermissions.toString(8)
// 仅取后三位权限部分
if (octalPermissions.length < 3) {
return result
}
val permissionBits = octalPermissions.takeLast(3)
// 解析每一部分的权限
val owner = permissionBits[0].digitToInt()
val group = permissionBits[1].digitToInt()
val others = permissionBits[2].digitToInt()
// 处理所有者权限
if ((owner and 4) != 0) result.add(PosixFilePermission.OWNER_READ)
if ((owner and 2) != 0) result.add(PosixFilePermission.OWNER_WRITE)
if ((owner and 1) != 0) result.add(PosixFilePermission.OWNER_EXECUTE)
// 处理组权限
if ((group and 4) != 0) result.add(PosixFilePermission.GROUP_READ)
if ((group and 2) != 0) result.add(PosixFilePermission.GROUP_WRITE)
if ((group and 1) != 0) result.add(PosixFilePermission.GROUP_EXECUTE)
// 处理其他用户权限
if ((others and 4) != 0) result.add(PosixFilePermission.OTHERS_READ)
if ((others and 2) != 0) result.add(PosixFilePermission.OTHERS_WRITE)
if ((others and 1) != 0) result.add(PosixFilePermission.OTHERS_EXECUTE)
return result
}
}
var hasParent: Boolean = false
private set
override fun getValueAt(row: Int, column: Int): Any {
val file = getFileObject(row)
val isParentRow = hasParent && row == 0
try {
if (file.type == FileType.IMAGINARY) return StringUtils.EMPTY
return when (column) {
COLUMN_NAME -> if (isParentRow) ".." else file.name.baseName
COLUMN_FILE_SIZE -> if (isParentRow || file.isFolder) StringUtils.EMPTY else formatBytes(file.content.size)
COLUMN_TYPE -> if (isParentRow) StringUtils.EMPTY else getFileType(file)
COLUMN_LAST_MODIFIED_TIME -> if (isParentRow) StringUtils.EMPTY else getLastModifiedTime(file)
COLUMN_ATTRS -> if (isParentRow) StringUtils.EMPTY else getAttrs(file)
COLUMN_OWNER -> StringUtils.EMPTY
else -> StringUtils.EMPTY
}
} catch (e: Exception) {
if (file.fileSystem is LocalFileSystem) {
if (ExceptionUtils.getRootCause(e) is java.nio.file.NoSuchFileException) {
SwingUtilities.invokeLater { removeRow(row) }
return StringUtils.EMPTY
}
}
if (log.isWarnEnabled) {
log.warn(e.message, e)
}
return StringUtils.EMPTY
}
}
private fun getFileType(file: FileObject): String {
return if (SystemInfo.isWindows) NativeFileIcons.getIcon(file.name.baseName, file.isFile).second
else if (file.isSymbolicLink) I18n.getString("termora.transport.table.type.symbolic-link")
else NativeFileIcons.getIcon(file.name.baseName, file.isFile).second
}
fun getFileIcon(file: FileObject, width: Int = 16, height: Int = 16): Icon {
return if (SystemInfo.isWindows) NativeFileIcons.getIcon(file.name.baseName, file.isFile, width, height).first
else NativeFileIcons.getIcon(file.name.baseName, file.isFile).first
}
fun getFileIcon(row: Int): Icon {
return getFileIcon(getFileObject(row))
}
fun getLastModifiedTime(file: FileObject): String {
if (file.content.lastModifiedTime < 1) return "-"
return DateFormatUtils.format(Date(file.content.lastModifiedTime), "yyyy/MM/dd HH:mm")
}
private fun getAttrs(file: FileObject): String {
if (file.fileSystem is LocalFileSystem) return StringUtils.EMPTY
return PosixFilePermissions.toString(getFilePermissions(file))
}
fun getFilePermissions(file: FileObject): Set<PosixFilePermission> {
val permissions = file.content.getAttribute(MySftpFileObject.POSIX_FILE_PERMISSIONS)
as Int? ?: return emptySet()
return fromSftpPermissions(permissions)
}
override fun getDataVector(): Vector<Vector<Any>> {
return super.getDataVector()
}
override fun getColumnCount(): Int {
return 6
}
override fun getColumnClass(columnIndex: Int): Class<*> {
return when (columnIndex) {
COLUMN_NAME -> String::class.java
else -> super.getColumnClass(columnIndex)
}
}
fun getFileObject(row: Int): FileObject {
return super.getValueAt(row, 0) as FileObject
}
fun getPathNames(): Set<String> {
val names = linkedSetOf<String>()
for (i in 0 until rowCount) {
if (hasParent && i == 0) {
names.add("..")
} else {
names.add(getFileObject(i).name.baseName)
}
}
return names
}
override fun getColumnName(column: Int): String {
return when (column) {
COLUMN_NAME -> I18n.getString("termora.transport.table.filename")
COLUMN_FILE_SIZE -> I18n.getString("termora.transport.table.size")
COLUMN_TYPE -> I18n.getString("termora.transport.table.type")
COLUMN_LAST_MODIFIED_TIME -> I18n.getString("termora.transport.table.modified-time")
COLUMN_ATTRS -> I18n.getString("termora.transport.table.permissions")
COLUMN_OWNER -> I18n.getString("termora.transport.table.owner")
else -> StringUtils.EMPTY
}
}
override fun isCellEditable(row: Int, column: Int): Boolean {
return false
}
suspend fun reload(dir: FileObject, useFileHiding: Boolean) {
if (log.isDebugEnabled) {
log.debug("Reloading {} , useFileHiding {}", dir, useFileHiding)
}
val files = mutableListOf<FileObject>()
withContext(Dispatchers.IO) {
dir.refresh()
for (file in dir.children) {
if (useFileHiding && file.isHidden) continue
files.add(file)
}
}
files.sortWith(compareBy<FileObject> { !it.isFolder }.thenComparing { a, b ->
NativeStringComparator.getInstance().compare(
a.name.baseName,
b.name.baseName
)
})
hasParent = dir.parent != null
if (hasParent) {
files.addFirst(dir.parent)
}
withContext(Dispatchers.Swing) {
while (rowCount > 0) removeRow(0)
files.forEach { addRow(arrayOf(it)) }
}
}
}