ftp improved

This commit is contained in:
Radek Davidek 2026-04-21 18:49:16 +02:00
parent a810cfac02
commit 14d386f736
5 changed files with 155 additions and 6 deletions

View File

@ -489,6 +489,17 @@ public class FtpService {
return pwd;
}
/**
* Explicitly close connection for a profile if any is cached.
* Note: Current infrastructure creates/closes connections per operation,
* but this provides a hook for future persistent connection management.
*/
public static void disconnect(FtpProfile profile) {
if (profile == null) return;
log("DISCONNECT requested for profile: " + profile.getName() + " (" + profile.getHost() + ")");
// When persistent connections are implemented, they should be closed here.
}
/**
* Parse an FTP URL (ftp://host:port/path) and find matching profile from AppConfig.
*/
@ -527,7 +538,7 @@ public class FtpService {
return FTP_PREFIX + profile.getHost() + ":" + profile.getPort() + remotePath;
}
private static String getNameFromPath(String path) {
public static String getNameFromPath(String path) {
if (path == null || path.equals("/") || path.isEmpty()) return "";
int lastSlash = path.lastIndexOf('/');
return path.substring(lastSlash + 1);

View File

@ -29,6 +29,12 @@ public class FileEditor extends JFrame {
private boolean readOnly;
private JLabel statusPosLabel;
private JLabel statusSelLabel;
// FTP upload support
private cz.kamma.kfmanager.model.FtpProfile ftpProfile;
private String ftpPath;
private Runnable onSaveSuccess;
// Hex view support
private boolean hexMode = false;
private byte[] fileBytes = null;
@ -112,6 +118,15 @@ public class FileEditor extends JFrame {
});
}
/**
* Set FTP details for automatic upload after saving.
*/
public void setFtpDetails(cz.kamma.kfmanager.model.FtpProfile profile, String remotePath, Runnable onSaveSuccess) {
this.ftpProfile = profile;
this.ftpPath = remotePath;
this.onSaveSuccess = onSaveSuccess;
}
private void initSearchPanel() {
searchPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 2));
searchPanel.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.GRAY));
@ -959,7 +974,7 @@ public class FileEditor extends JFrame {
}
private void loadFile() {
if (virtualPath != null) {
if (virtualPath != null && ftpProfile == null) {
try {
fileBytes = cz.kamma.kfmanager.service.FileOperations.readFileFromArchive(file, virtualPath);
boolean binary = isBinary(fileBytes);
@ -1212,6 +1227,40 @@ public class FileEditor extends JFrame {
Files.write(file.toPath(), content.getBytes("UTF-8"));
modified = false;
updateTitle();
if (ftpProfile != null && ftpPath != null) {
final cz.kamma.kfmanager.model.FtpProfile finalProfile = ftpProfile;
final String finalPath = ftpPath;
final File finalFile = file;
// Use the main window's common operation runner if possible, or just background it here.
// Since FileEditor works standalone too, we'll use a simple SwingWorker.
new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
cz.kamma.kfmanager.service.FtpService.uploadFile(finalProfile, finalFile, finalPath, null);
return null;
}
@Override
protected void done() {
try {
get();
if (onSaveSuccess != null) {
onSaveSuccess.run();
}
JOptionPane.showMessageDialog(FileEditor.this, "File saved and uploaded to FTP", "Success", JOptionPane.INFORMATION_MESSAGE);
} catch (Exception ex) {
JOptionPane.showMessageDialog(FileEditor.this,
"Successfully saved locally, but failed to upload to FTP:\n" + ex.getMessage(),
"FTP Upload Error",
JOptionPane.ERROR_MESSAGE);
}
}
}.execute();
return; // message handled in SwingWorker
}
JOptionPane.showMessageDialog(this,
"File saved",
"Success",

View File

@ -440,6 +440,8 @@ public class FilePanel extends JPanel {
// Extract display name from FTP URL for tab title
String tabTitle = getFtpTabTitle(ftpPath);
applyConfiguredAppearance(tab);
tabbedPane.addTab(tabTitle, tab);
tabbedPane.setSelectedComponent(tab);
@ -788,9 +790,11 @@ public class FilePanel extends JPanel {
Component comp = tabbedPane.getComponentAt(i);
if (comp instanceof FilePanelTab tab && tab.isFtpTab()) {
if (tabbedPane.getTabCount() > 1) {
tab.cleanup();
tabbedPane.removeTabAt(i);
} else {
// If it's the last tab, reset it to home instead of removing
tab.cleanup();
tab.loadDirectory(new File(System.getProperty("user.home")));
}
}
@ -805,6 +809,10 @@ public class FilePanel extends JPanel {
public void closeCurrentTab() {
int index = tabbedPane.getSelectedIndex();
if (index >= 0 && tabbedPane.getTabCount() > 1) {
Component comp = tabbedPane.getComponentAt(index);
if (comp instanceof FilePanelTab tab) {
tab.cleanup();
}
tabbedPane.removeTabAt(index);
updatePathField();
updateTabStyles();

View File

@ -1624,8 +1624,14 @@ public class FilePanelTab extends JPanel {
*/
public void navigateFtpUp() {
if (ftpCurrentPath == null || ftpCurrentPath.equals("/")) return;
String previousDirName = FtpService.getNameFromPath(ftpCurrentPath);
String parentPath = FtpService.getParentPath(ftpCurrentPath);
loadFtpDirectory(parentPath, true, true);
loadFtpDirectory(parentPath, false, true);
// Select the directory we just left
if (previousDirName != null && !previousDirName.isEmpty()) {
SwingUtilities.invokeLater(() -> selectItemByName(previousDirName));
}
}
/**
@ -4323,6 +4329,15 @@ public class FilePanelTab extends JPanel {
updateSortButtonsDisplay();
}
/**
* Cleanup resources when tab is closed.
*/
public void cleanup() {
if (isFtpTab && ftpProfile != null) {
FtpService.disconnect(ftpProfile);
}
}
// Getters
public JTable getFileTable() {
return fileTable;

View File

@ -17,6 +17,7 @@ import java.awt.datatransfer.DataFlavor;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
@ -2437,6 +2438,30 @@ public class MainWindow extends JFrame {
return;
}
if (item.isFtp()) {
try {
File tempFile = Files.createTempFile("kf-ftp-view-", "-" + item.getName()).toFile();
tempFile.deleteOnExit();
performFileOperation((cb) -> {
cz.kamma.kfmanager.service.FtpService.downloadFile(item.getFtpProfile(), item.getFtpPath(), tempFile, cb);
}, "Download finished", false, false, () -> {
FileEditor viewer = new FileEditor(this, tempFile, item.getFtpPath(), config, true);
viewer.addWindowListener(new java.awt.event.WindowAdapter() {
@Override
public void windowClosed(java.awt.event.WindowEvent e) {
try { tempFile.delete(); } catch(Exception ignore) {}
requestFocusInActivePanel();
}
});
viewer.setVisible(true);
});
} catch (IOException e) {
JOptionPane.showMessageDialog(this, "Could not create temp file: " + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
return;
}
File file = item.getFile();
// Removed previous 10 MB limit: allow opening large files in the viewer (paged hex will stream large binaries).
@ -2465,6 +2490,49 @@ public class MainWindow extends JFrame {
return;
}
if (item.isFtp()) {
try {
File tempFile = Files.createTempFile("kf-ftp-edit-", "-" + item.getName()).toFile();
tempFile.deleteOnExit();
performFileOperation((cb) -> {
cz.kamma.kfmanager.service.FtpService.downloadFile(item.getFtpProfile(), item.getFtpPath(), tempFile, cb);
}, "Download finished", false, false, () -> {
// Check if an external editor is configured
String ext = config.getExternalEditorPath();
if (ext != null && !ext.trim().isEmpty()) {
try {
java.util.List<String> cmd = new java.util.ArrayList<>();
cmd.add(ext);
cmd.add(tempFile.getAbsolutePath());
Process p = new ProcessBuilder(cmd).start();
// Optional: Monitor process to upload back on exit?
// For now internal editor is safer for auto-upload.
} catch (Exception ex) {
JOptionPane.showMessageDialog(this, "External editor failed: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
}
FileEditor editor = new FileEditor(this, tempFile, item.getFtpPath(), config, false);
editor.setFtpDetails(item.getFtpProfile(), item.getFtpPath(), () -> {
// Success callback after upload
if (activePanel != null) activePanel.refresh(false);
});
editor.addWindowListener(new java.awt.event.WindowAdapter() {
@Override
public void windowClosed(java.awt.event.WindowEvent e) {
try { tempFile.delete(); } catch(Exception ignore) {}
requestFocusInActivePanel();
}
});
editor.setVisible(true);
});
} catch (IOException e) {
JOptionPane.showMessageDialog(this, "Could not create temp file: " + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
return;
}
File file = item.getFile();
// If an external editor is configured, try launching it with the file path
@ -3302,9 +3370,7 @@ public class MainWindow extends JFrame {
MainWindow.this.toFront();
for (FilePanel panel : panelsToRefresh) {
if (panel.getCurrentDirectory() != null) {
panel.loadDirectory(panel.getCurrentDirectory(), false, false);
}
panel.refresh(false);
}
if (postTask != null) {