feat: modify permissions to support recursion (#571)

This commit is contained in:
hstyi
2025-05-12 15:26:29 +08:00
committed by GitHub
parent 1de10e6129
commit ea25b5b46f
7 changed files with 207 additions and 103 deletions

View File

@@ -4,6 +4,7 @@ import app.termora.*
import app.termora.actions.AnActionEvent import app.termora.actions.AnActionEvent
import app.termora.actions.SettingsAction import app.termora.actions.SettingsAction
import app.termora.sftp.FileSystemViewTable.AskTransfer.Action import app.termora.sftp.FileSystemViewTable.AskTransfer.Action
import app.termora.vfs2.VFSWalker
import app.termora.vfs2.sftp.MySftpFileObject import app.termora.vfs2.sftp.MySftpFileObject
import app.termora.vfs2.sftp.MySftpFileSystem import app.termora.vfs2.sftp.MySftpFileSystem
import com.formdev.flatlaf.FlatClientProperties import com.formdev.flatlaf.FlatClientProperties
@@ -37,7 +38,6 @@ import java.nio.file.FileVisitor
import java.nio.file.Paths import java.nio.file.Paths
import java.nio.file.StandardOpenOption import java.nio.file.StandardOpenOption
import java.nio.file.attribute.BasicFileAttributes import java.nio.file.attribute.BasicFileAttributes
import java.nio.file.attribute.FileTime
import java.text.MessageFormat import java.text.MessageFormat
import java.util.* import java.util.*
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@@ -45,6 +45,21 @@ import java.util.regex.Pattern
import javax.swing.* import javax.swing.*
import javax.swing.table.DefaultTableCellRenderer import javax.swing.table.DefaultTableCellRenderer
import kotlin.collections.ArrayDeque import kotlin.collections.ArrayDeque
import kotlin.collections.List
import kotlin.collections.all
import kotlin.collections.contains
import kotlin.collections.filter
import kotlin.collections.filterIsInstance
import kotlin.collections.find
import kotlin.collections.forEach
import kotlin.collections.isEmpty
import kotlin.collections.isNotEmpty
import kotlin.collections.last
import kotlin.collections.listOf
import kotlin.collections.map
import kotlin.collections.mapOf
import kotlin.collections.mutableListOf
import kotlin.collections.sortedArray
import kotlin.io.path.absolutePathString import kotlin.io.path.absolutePathString
import kotlin.math.max import kotlin.math.max
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
@@ -360,34 +375,7 @@ class FileSystemViewTable(
override fun actionPerformed(e: ActionEvent) { override fun actionPerformed(e: ActionEvent) {
val last = files.last() val last = files.last()
if (last !is MySftpFileObject) return if (last !is MySftpFileObject) return
changePermission(last)
val dialog = PosixFilePermissionDialog(
SwingUtilities.getWindowAncestor(table),
model.getFilePermissions(last)
)
val permissions = dialog.open() ?: return
if (fileSystemViewPanel.requestLoading()) {
coroutineScope.launch(Dispatchers.IO) {
val c = runCatching { last.setPosixFilePermissions(permissions) }.onFailure {
withContext(Dispatchers.Swing) {
OptionPane.showMessageDialog(
owner,
ExceptionUtils.getMessage(it),
messageType = JOptionPane.ERROR_MESSAGE
)
}
}
// stop loading
fileSystemViewPanel.stopLoading()
// reload
if (c.isSuccess) {
fileSystemViewPanel.reload(true)
}
}
}
} }
}) })
refresh.addActionListener { fileSystemViewPanel.reload() } refresh.addActionListener { fileSystemViewPanel.reload() }
@@ -409,6 +397,80 @@ class FileSystemViewTable(
popupMenu.show(table, e.x, e.y) popupMenu.show(table, e.x, e.y)
} }
private fun changePermission(file: MySftpFileObject) {
val dialog = PosixFilePermissionDialog(
SwingUtilities.getWindowAncestor(table),
model.getFilePermissions(file)
)
val permissions = dialog.open() ?: return
val isIncludeSubdirectories = dialog.isIncludeSubdirectories()
if (fileSystemViewPanel.requestLoading()) {
coroutineScope.launch(Dispatchers.IO) {
val c = runCatching {
file.setPosixFilePermissions(permissions)
if (isIncludeSubdirectories && file.isFolder) {
file.refresh()
VFSWalker.walk(file, object : FileVisitor<FileObject> {
override fun preVisitDirectory(
dir: FileObject,
attrs: BasicFileAttributes
): FileVisitResult {
dir.refresh()
if (dir is MySftpFileObject) {
dir.setPosixFilePermissions(permissions)
}
return FileVisitResult.CONTINUE
}
override fun visitFile(
file: FileObject,
attrs: BasicFileAttributes
): FileVisitResult {
if (file is MySftpFileObject) {
file.setPosixFilePermissions(permissions)
}
return FileVisitResult.CONTINUE
}
override fun visitFileFailed(
file: FileObject,
exc: IOException
): FileVisitResult {
return FileVisitResult.TERMINATE
}
override fun postVisitDirectory(
dir: FileObject,
exc: IOException?
): FileVisitResult {
return FileVisitResult.CONTINUE
}
})
}
}.onFailure {
withContext(Dispatchers.Swing) {
OptionPane.showMessageDialog(
owner,
ExceptionUtils.getMessage(it),
messageType = JOptionPane.ERROR_MESSAGE
)
}
}
// stop loading
fileSystemViewPanel.stopLoading()
// reload
if (c.isSuccess) {
fileSystemViewPanel.reload(true)
}
}
}
}
private fun renameSelection() { private fun renameSelection() {
val index = selectedRow val index = selectedRow
if (index < 0) return if (index < 0) return
@@ -886,36 +948,7 @@ class FileSystemViewTable(
dir: FileObject, dir: FileObject,
visitor: FileVisitor<FileObject>, visitor: FileVisitor<FileObject>,
): FileVisitResult { ): FileVisitResult {
return VFSWalker.walk(dir, visitor)
// clear cache
if (visitor.preVisitDirectory(dir, EmptyBasicFileAttributes.INSTANCE) == FileVisitResult.TERMINATE) {
return FileVisitResult.TERMINATE
}
for (e in dir.children) {
if (e.name.baseName == ".." || e.name.baseName == ".") continue
if (e.isFolder) {
if (walk(dir.resolveFile(e.name.baseName), visitor) == FileVisitResult.TERMINATE) {
return FileVisitResult.TERMINATE
}
} else {
val result = visitor.visitFile(
dir.resolveFile(e.name.baseName),
EmptyBasicFileAttributes.INSTANCE
)
if (result == FileVisitResult.TERMINATE) {
return FileVisitResult.TERMINATE
} else if (result == FileVisitResult.SKIP_SUBTREE) {
break
}
}
}
if (visitor.postVisitDirectory(dir, null) == FileVisitResult.TERMINATE) {
return FileVisitResult.TERMINATE
}
return FileVisitResult.CONTINUE
} }
private fun addTransport( private fun addTransport(
@@ -974,47 +1007,5 @@ class FileSystemViewTable(
} }
private class EmptyBasicFileAttributes : BasicFileAttributes {
companion object {
val INSTANCE = EmptyBasicFileAttributes()
}
override fun lastModifiedTime(): FileTime {
TODO("Not yet implemented")
}
override fun lastAccessTime(): FileTime {
TODO("Not yet implemented")
}
override fun creationTime(): FileTime {
TODO("Not yet implemented")
}
override fun isRegularFile(): Boolean {
TODO("Not yet implemented")
}
override fun isDirectory(): Boolean {
TODO("Not yet implemented")
}
override fun isSymbolicLink(): Boolean {
TODO("Not yet implemented")
}
override fun isOther(): Boolean {
TODO("Not yet implemented")
}
override fun size(): Long {
TODO("Not yet implemented")
}
override fun fileKey(): Any {
TODO("Not yet implemented")
}
}
} }

View File

@@ -157,7 +157,11 @@ class FileSystemViewTableModel : DefaultTableModel() {
fun getPathNames(): Set<String> { fun getPathNames(): Set<String> {
val names = linkedSetOf<String>() val names = linkedSetOf<String>()
for (i in 0 until rowCount) { for (i in 0 until rowCount) {
names.add(getFileObject(i).name.baseName) if (hasParent && i == 0) {
names.add("..")
} else {
names.add(getFileObject(i).name.baseName)
}
} }
return names return names
} }

View File

@@ -25,6 +25,7 @@ class PosixFilePermissionDialog(
private val otherRead = JCheckBox(I18n.getString("termora.transport.permissions.read")) private val otherRead = JCheckBox(I18n.getString("termora.transport.permissions.read"))
private val otherWrite = JCheckBox(I18n.getString("termora.transport.permissions.write")) private val otherWrite = JCheckBox(I18n.getString("termora.transport.permissions.write"))
private val otherExecute = JCheckBox(I18n.getString("termora.transport.permissions.execute")) private val otherExecute = JCheckBox(I18n.getString("termora.transport.permissions.execute"))
private val includeSubFolder = JCheckBox(I18n.getString("termora.transport.permissions.include-subfolder"))
private var isCancelled = false private var isCancelled = false
@@ -60,13 +61,14 @@ class PosixFilePermissionDialog(
otherRead.isFocusable = false otherRead.isFocusable = false
otherWrite.isFocusable = false otherWrite.isFocusable = false
otherExecute.isFocusable = false otherExecute.isFocusable = false
includeSubFolder.isFocusable = false
} }
override fun createCenterPanel(): JComponent { override fun createCenterPanel(): JComponent {
val formMargin = "7dlu" val formMargin = "7dlu"
val layout = FormLayout( val layout = FormLayout(
"default:grow, $formMargin, default:grow, $formMargin, default:grow", "default:grow, $formMargin, default:grow, $formMargin, default:grow",
"pref, $formMargin, pref, $formMargin, pref" "pref, $formMargin, pref, $formMargin, pref, $formMargin, pref"
) )
val builder = FormBuilder.create().padding("0, $formMargin, $formMargin, $formMargin") val builder = FormBuilder.create().padding("0, $formMargin, $formMargin, $formMargin")
@@ -95,6 +97,8 @@ class PosixFilePermissionDialog(
otherBox.border = BorderFactory.createTitledBorder(I18n.getString("termora.transport.permissions.others")) otherBox.border = BorderFactory.createTitledBorder(I18n.getString("termora.transport.permissions.others"))
builder.add(otherBox).xy(5, 3) builder.add(otherBox).xy(5, 3)
builder.add(includeSubFolder).xyw(1, 5, 5)
return builder.build() return builder.build()
} }
@@ -103,6 +107,10 @@ class PosixFilePermissionDialog(
super.doCancelAction() super.doCancelAction()
} }
fun isIncludeSubdirectories(): Boolean {
return includeSubFolder.isSelected
}
/** /**
* @return 返回空表示取消了 * @return 返回空表示取消了
*/ */

View File

@@ -0,0 +1,88 @@
package app.termora.vfs2
import org.apache.commons.vfs2.FileObject
import java.nio.file.FileVisitResult
import java.nio.file.FileVisitor
import java.nio.file.attribute.BasicFileAttributes
import java.nio.file.attribute.FileTime
object VFSWalker {
fun walk(
dir: FileObject,
visitor: FileVisitor<FileObject>,
): FileVisitResult {
// clear cache
if (visitor.preVisitDirectory(dir, EmptyBasicFileAttributes.INSTANCE) == FileVisitResult.TERMINATE) {
return FileVisitResult.TERMINATE
}
for (e in dir.children) {
if (e.name.baseName == ".." || e.name.baseName == ".") continue
if (e.isFolder) {
if (walk(dir.resolveFile(e.name.baseName), visitor) == FileVisitResult.TERMINATE) {
return FileVisitResult.TERMINATE
}
} else {
val result = visitor.visitFile(
dir.resolveFile(e.name.baseName),
EmptyBasicFileAttributes.INSTANCE
)
if (result == FileVisitResult.TERMINATE) {
return FileVisitResult.TERMINATE
} else if (result == FileVisitResult.SKIP_SUBTREE) {
break
}
}
}
if (visitor.postVisitDirectory(dir, null) == FileVisitResult.TERMINATE) {
return FileVisitResult.TERMINATE
}
return FileVisitResult.CONTINUE
}
private class EmptyBasicFileAttributes : BasicFileAttributes {
companion object {
val INSTANCE = EmptyBasicFileAttributes()
}
override fun lastModifiedTime(): FileTime {
TODO("Not yet implemented")
}
override fun lastAccessTime(): FileTime {
TODO("Not yet implemented")
}
override fun creationTime(): FileTime {
TODO("Not yet implemented")
}
override fun isRegularFile(): Boolean {
TODO("Not yet implemented")
}
override fun isDirectory(): Boolean {
TODO("Not yet implemented")
}
override fun isSymbolicLink(): Boolean {
TODO("Not yet implemented")
}
override fun isOther(): Boolean {
TODO("Not yet implemented")
}
override fun size(): Long {
TODO("Not yet implemented")
}
override fun fileKey(): Any {
TODO("Not yet implemented")
}
}
}

View File

@@ -310,6 +310,7 @@ termora.transport.permissions.execute=Execute
termora.transport.permissions.owner=Owner termora.transport.permissions.owner=Owner
termora.transport.permissions.group=Group termora.transport.permissions.group=Group
termora.transport.permissions.others=Others termora.transport.permissions.others=Others
termora.transport.permissions.include-subfolder=Include subdirectories
termora.transport.sftp.retry=Retry termora.transport.sftp.retry=Retry
termora.transport.sftp.select-another-host=Select another host termora.transport.sftp.select-another-host=Select another host

View File

@@ -323,6 +323,7 @@ termora.transport.permissions.execute=执行
termora.transport.permissions.owner=所有者 termora.transport.permissions.owner=所有者
termora.transport.permissions.group= termora.transport.permissions.group=
termora.transport.permissions.others=其他 termora.transport.permissions.others=其他
termora.transport.permissions.include-subfolder=包含子目录
# transport job # transport job
termora.transport.jobs.table.name=名称 termora.transport.jobs.table.name=名称

View File

@@ -306,6 +306,17 @@ termora.transport.sftp.already-exists.destination=目標文件
termora.transport.sftp.already-exists.source=原始檔 termora.transport.sftp.already-exists.source=原始檔
termora.transport.sftp.already-exists.actions=操作 termora.transport.sftp.already-exists.actions=操作
# permissions
termora.transport.permissions=更改權限
termora.transport.permissions.file-folder-permissions=檔案/資料夾權限
termora.transport.permissions.read=讀取
termora.transport.permissions.write=寫入
termora.transport.permissions.execute=執行
termora.transport.permissions.owner=所有者
termora.transport.permissions.group=群組
termora.transport.permissions.others=其他
termora.transport.permissions.include-subfolder=包含子目錄
# transport job # transport job
termora.transport.jobs.table.name=名稱 termora.transport.jobs.table.name=名稱
termora.transport.jobs.table.status=狀態 termora.transport.jobs.table.status=狀態