mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
feat: modify permissions to support recursion (#571)
This commit is contained in:
@@ -4,6 +4,7 @@ import app.termora.*
|
||||
import app.termora.actions.AnActionEvent
|
||||
import app.termora.actions.SettingsAction
|
||||
import app.termora.sftp.FileSystemViewTable.AskTransfer.Action
|
||||
import app.termora.vfs2.VFSWalker
|
||||
import app.termora.vfs2.sftp.MySftpFileObject
|
||||
import app.termora.vfs2.sftp.MySftpFileSystem
|
||||
import com.formdev.flatlaf.FlatClientProperties
|
||||
@@ -37,7 +38,6 @@ import java.nio.file.FileVisitor
|
||||
import java.nio.file.Paths
|
||||
import java.nio.file.StandardOpenOption
|
||||
import java.nio.file.attribute.BasicFileAttributes
|
||||
import java.nio.file.attribute.FileTime
|
||||
import java.text.MessageFormat
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
@@ -45,6 +45,21 @@ import java.util.regex.Pattern
|
||||
import javax.swing.*
|
||||
import javax.swing.table.DefaultTableCellRenderer
|
||||
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.math.max
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
@@ -360,34 +375,7 @@ class FileSystemViewTable(
|
||||
override fun actionPerformed(e: ActionEvent) {
|
||||
val last = files.last()
|
||||
if (last !is MySftpFileObject) return
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
changePermission(last)
|
||||
}
|
||||
})
|
||||
refresh.addActionListener { fileSystemViewPanel.reload() }
|
||||
@@ -409,6 +397,80 @@ class FileSystemViewTable(
|
||||
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() {
|
||||
val index = selectedRow
|
||||
if (index < 0) return
|
||||
@@ -886,36 +948,7 @@ class FileSystemViewTable(
|
||||
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
|
||||
return VFSWalker.walk(dir, visitor)
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -157,7 +157,11 @@ class FileSystemViewTableModel : DefaultTableModel() {
|
||||
fun getPathNames(): Set<String> {
|
||||
val names = linkedSetOf<String>()
|
||||
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
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ class PosixFilePermissionDialog(
|
||||
private val otherRead = JCheckBox(I18n.getString("termora.transport.permissions.read"))
|
||||
private val otherWrite = JCheckBox(I18n.getString("termora.transport.permissions.write"))
|
||||
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
|
||||
|
||||
@@ -60,13 +61,14 @@ class PosixFilePermissionDialog(
|
||||
otherRead.isFocusable = false
|
||||
otherWrite.isFocusable = false
|
||||
otherExecute.isFocusable = false
|
||||
includeSubFolder.isFocusable = false
|
||||
}
|
||||
|
||||
override fun createCenterPanel(): JComponent {
|
||||
val formMargin = "7dlu"
|
||||
val layout = FormLayout(
|
||||
"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")
|
||||
@@ -95,6 +97,8 @@ class PosixFilePermissionDialog(
|
||||
otherBox.border = BorderFactory.createTitledBorder(I18n.getString("termora.transport.permissions.others"))
|
||||
builder.add(otherBox).xy(5, 3)
|
||||
|
||||
builder.add(includeSubFolder).xyw(1, 5, 5)
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
@@ -103,6 +107,10 @@ class PosixFilePermissionDialog(
|
||||
super.doCancelAction()
|
||||
}
|
||||
|
||||
fun isIncludeSubdirectories(): Boolean {
|
||||
return includeSubFolder.isSelected
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 返回空表示取消了
|
||||
*/
|
||||
|
||||
88
src/main/kotlin/app/termora/vfs2/VFSWalker.kt
Normal file
88
src/main/kotlin/app/termora/vfs2/VFSWalker.kt
Normal 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")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -310,6 +310,7 @@ termora.transport.permissions.execute=Execute
|
||||
termora.transport.permissions.owner=Owner
|
||||
termora.transport.permissions.group=Group
|
||||
termora.transport.permissions.others=Others
|
||||
termora.transport.permissions.include-subfolder=Include subdirectories
|
||||
|
||||
termora.transport.sftp.retry=Retry
|
||||
termora.transport.sftp.select-another-host=Select another host
|
||||
|
||||
@@ -323,6 +323,7 @@ termora.transport.permissions.execute=执行
|
||||
termora.transport.permissions.owner=所有者
|
||||
termora.transport.permissions.group=组
|
||||
termora.transport.permissions.others=其他
|
||||
termora.transport.permissions.include-subfolder=包含子目录
|
||||
|
||||
# transport job
|
||||
termora.transport.jobs.table.name=名称
|
||||
|
||||
@@ -306,6 +306,17 @@ termora.transport.sftp.already-exists.destination=目標文件
|
||||
termora.transport.sftp.already-exists.source=原始檔
|
||||
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
|
||||
termora.transport.jobs.table.name=名稱
|
||||
termora.transport.jobs.table.status=狀態
|
||||
|
||||
Reference in New Issue
Block a user