Skip to content

Commit

Permalink
feat: supports importing hosts from FinalShell (#295)
Browse files Browse the repository at this point in the history
  • Loading branch information
hstyi authored Feb 22, 2025
1 parent 0552917 commit 05fe6a0
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 4 deletions.
58 changes: 54 additions & 4 deletions src/main/kotlin/app/termora/NewHostTree.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import org.apache.commons.csv.CSVParser
import org.apache.commons.csv.CSVPrinter
import org.apache.commons.io.FileUtils
import org.apache.commons.io.FilenameUtils
import org.apache.commons.io.filefilter.FileFilterUtils
import org.apache.commons.lang3.StringUtils
import org.apache.commons.lang3.exception.ExceptionUtils
import org.ini4j.Ini
Expand Down Expand Up @@ -365,6 +366,7 @@ class NewHostTree : JXTree() {
val importMenu = JMenu(I18n.getString("termora.welcome.contextmenu.import"))
val csvMenu = importMenu.add("CSV")
val xShellMenu = importMenu.add("Xshell")
val finalShellMenu = importMenu.add("FinalShell")
val windTermMenu = importMenu.add("WindTerm")
val secureCRTMenu = importMenu.add("SecureCRT")
val mobaXtermMenu = importMenu.add("MobaXterm")
Expand Down Expand Up @@ -398,6 +400,7 @@ class NewHostTree : JXTree() {
xShellMenu.addActionListener { importHosts(lastNode, ImportType.Xshell) }
secureCRTMenu.addActionListener { importHosts(lastNode, ImportType.SecureCRT) }
mobaXtermMenu.addActionListener { importHosts(lastNode, ImportType.MobaXterm) }
finalShellMenu.addActionListener { importHosts(lastNode, ImportType.FinalShell) }
csvMenu.addActionListener { importHosts(lastNode, ImportType.CSV) }
windTermMenu.addActionListener { importHosts(lastNode, ImportType.WindTerm) }
open.addActionListener { openHosts(it, false) }
Expand Down Expand Up @@ -656,6 +659,11 @@ class NewHostTree : JXTree() {
chooser.dialogTitle = "Xshell Sessions"
chooser.isAcceptAllFileFilterUsed = true
}

ImportType.FinalShell -> {
chooser.fileSelectionMode = JFileChooser.DIRECTORIES_ONLY
chooser.isAcceptAllFileFilterUsed = true
}
}

val dir = properties.getString("NewHostTree.ImportHosts.defaultDir", StringUtils.EMPTY)
Expand Down Expand Up @@ -706,20 +714,22 @@ class NewHostTree : JXTree() {
// 选择文件
val code = chooser.showOpenDialog(owner)

// 记住目录
properties.putString("NewHostTree.ImportHosts.defaultDir", chooser.currentDirectory.absolutePath)

if (code != JFileChooser.APPROVE_OPTION) {
return
}

val file = chooser.selectedFile
properties.putString(
"NewHostTree.ImportHosts.defaultDir",
(if (FileUtils.isDirectory(file)) file else file.parentFile).absolutePath
)

val nodes = when (type) {
ImportType.WindTerm -> parseFromWindTerm(folder, file)
ImportType.SecureCRT -> parseFromSecureCRT(folder, file)
ImportType.MobaXterm -> parseFromMobaXterm(folder, file)
ImportType.Xshell -> parseFromXshell(folder, file)
ImportType.FinalShell -> parseFromFinalShell(folder, file)
ImportType.CSV -> file.bufferedReader().use { parseFromCSV(folder, it) }
}

Expand Down Expand Up @@ -874,6 +884,45 @@ class NewHostTree : JXTree() {
return parseFromCSV(folder, StringReader(sw.toString()))
}

private fun parseFromFinalShell(folder: HostTreeNode, dir: File): List<HostTreeNode> {
val files = FileUtils.listFiles(
dir,
FileFilterUtils.suffixFileFilter("_connect_config.json"),
FileFilterUtils.trueFileFilter()
)

if (files.isEmpty()) {
OptionPane.showMessageDialog(
owner,
I18n.getString("termora.welcome.contextmenu.import.finalshell-folder-empty")
)
return emptyList()
}

val sw = StringWriter()
CSVPrinter(sw, CSVFormat.EXCEL.builder().setHeader(*CSV_HEADERS).get()).use { printer ->
for (file in files) {
try {
val json = ohMyJson.runCatching { ohMyJson.parseToJsonElement(file.readText()) }
.getOrNull()?.jsonObject ?: continue
val username = json["user_name"]?.jsonPrimitive?.content ?: StringUtils.EMPTY
val label = json["name"]?.jsonPrimitive?.content ?: StringUtils.EMPTY
val host = json["host"]?.jsonPrimitive?.content ?: StringUtils.EMPTY
val port = json["port"]?.jsonPrimitive?.intOrNull ?: 22
if (StringUtils.isAllBlank(host, label)) continue
val folders = FilenameUtils.separatorsToUnix(file.parentFile.relativeTo(dir).toString())
printer.printRecord(folders, StringUtils.defaultIfBlank(label, host), host, port, username, "SSH")
} catch (e: Exception) {
if (log.isErrorEnabled) {
log.error(file.absolutePath, e)
}
}
}
}

return parseFromCSV(folder, StringReader(sw.toString()))
}

private fun parseFromCSV(folderNode: HostTreeNode, sr: Reader): List<HostTreeNode> {
val records = CSVParser.builder()
.setFormat(CSVFormat.EXCEL.builder().setHeader(*CSV_HEADERS).setSkipHeaderRecord(true).get())
Expand Down Expand Up @@ -954,7 +1003,8 @@ class NewHostTree : JXTree() {
CSV,
Xshell,
SecureCRT,
MobaXterm
MobaXterm,
FinalShell,
}

private class MoveHostTransferable(val nodes: List<HostTreeNode>) : Transferable {
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/i18n/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ termora.welcome.contextmenu.import.csv.download-template=Do you want to import o
termora.welcome.contextmenu.import.csv.download-template-done=Download the template successfully
termora.welcome.contextmenu.import.csv.download-template-done-open-folder=Download the template successfully, Do you want to open the folder?
termora.welcome.contextmenu.import.xshell-folder-empty=The folder does not contain any *.xsh files, Please select the correct Xshell Sessions directory
termora.welcome.contextmenu.import.finalshell-folder-empty=The folder does not contain any *_connect_config.json files, Please select the correct FinalShell directory

# New Host
termora.new-host.title=Create a new host
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/i18n/messages_zh_CN.properties
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ termora.welcome.contextmenu.import.csv.download-template=您要导入还是下
termora.welcome.contextmenu.import.csv.download-template-done=下载成功
termora.welcome.contextmenu.import.csv.download-template-done-open-folder=下载成功, 是否需要打开所在文件夹?
termora.welcome.contextmenu.import.xshell-folder-empty=该文件夹不包含 *.xsh 文件,请选择正确的 Xshell 会话目录
termora.welcome.contextmenu.import.finalshell-folder-empty=该文件夹不包含 *_connect_config.json 文件,请选择正确的 FinalShell 配置目录

# New Host
termora.new-host.title=新建主机
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/i18n/messages_zh_TW.properties
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ termora.welcome.contextmenu.import.csv.download-template=您要匯入還是下
termora.welcome.contextmenu.import.csv.download-template-done=下載成功
termora.welcome.contextmenu.import.csv.download-template-done-open-folder=下載成功, 是否需要開啟所在資料夾?
termora.welcome.contextmenu.import.xshell-folder-empty=該資料夾不包含 *.xsh 文件,請選擇正確的 Xshell 會話目錄
termora.welcome.contextmenu.import.finalshell-folder-empty=該資料夾不包含 *_connect_config.json 文件,請選擇正確的 FinalShell 設定目錄

# New Host
termora.new-host.title=新主機
Expand Down

0 comments on commit 05fe6a0

Please sign in to comment.