diff --git a/src/main/java/cz/kamma/kfmanager/service/FtpService.java b/src/main/java/cz/kamma/kfmanager/service/FtpService.java index 95398b2..714441e 100644 --- a/src/main/java/cz/kamma/kfmanager/service/FtpService.java +++ b/src/main/java/cz/kamma/kfmanager/service/FtpService.java @@ -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); diff --git a/src/main/java/cz/kamma/kfmanager/ui/FileEditor.java b/src/main/java/cz/kamma/kfmanager/ui/FileEditor.java index 75308f0..8ad71e1 100644 --- a/src/main/java/cz/kamma/kfmanager/ui/FileEditor.java +++ b/src/main/java/cz/kamma/kfmanager/ui/FileEditor.java @@ -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() { + @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", diff --git a/src/main/java/cz/kamma/kfmanager/ui/FilePanel.java b/src/main/java/cz/kamma/kfmanager/ui/FilePanel.java index d848006..9d8cbdf 100644 --- a/src/main/java/cz/kamma/kfmanager/ui/FilePanel.java +++ b/src/main/java/cz/kamma/kfmanager/ui/FilePanel.java @@ -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(); diff --git a/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java b/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java index 03aa5bb..5ba9f30 100644 --- a/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java +++ b/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java @@ -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; diff --git a/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java b/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java index 985614c..3027d66 100644 --- a/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java +++ b/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java @@ -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; @@ -2436,6 +2437,30 @@ public class MainWindow extends JFrame { if (item.isDirectory() || item.getName().equals("..")) { 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(); @@ -2464,6 +2489,49 @@ public class MainWindow extends JFrame { if (item.isDirectory() || item.getName().equals("..")) { 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 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(); @@ -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) {