ftp improved
This commit is contained in:
parent
a810cfac02
commit
14d386f736
@ -489,6 +489,17 @@ public class FtpService {
|
|||||||
return pwd;
|
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.
|
* 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;
|
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 "";
|
if (path == null || path.equals("/") || path.isEmpty()) return "";
|
||||||
int lastSlash = path.lastIndexOf('/');
|
int lastSlash = path.lastIndexOf('/');
|
||||||
return path.substring(lastSlash + 1);
|
return path.substring(lastSlash + 1);
|
||||||
|
|||||||
@ -29,6 +29,12 @@ public class FileEditor extends JFrame {
|
|||||||
private boolean readOnly;
|
private boolean readOnly;
|
||||||
private JLabel statusPosLabel;
|
private JLabel statusPosLabel;
|
||||||
private JLabel statusSelLabel;
|
private JLabel statusSelLabel;
|
||||||
|
|
||||||
|
// FTP upload support
|
||||||
|
private cz.kamma.kfmanager.model.FtpProfile ftpProfile;
|
||||||
|
private String ftpPath;
|
||||||
|
private Runnable onSaveSuccess;
|
||||||
|
|
||||||
// Hex view support
|
// Hex view support
|
||||||
private boolean hexMode = false;
|
private boolean hexMode = false;
|
||||||
private byte[] fileBytes = null;
|
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() {
|
private void initSearchPanel() {
|
||||||
searchPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 2));
|
searchPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 2));
|
||||||
searchPanel.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.GRAY));
|
searchPanel.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.GRAY));
|
||||||
@ -959,7 +974,7 @@ public class FileEditor extends JFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void loadFile() {
|
private void loadFile() {
|
||||||
if (virtualPath != null) {
|
if (virtualPath != null && ftpProfile == null) {
|
||||||
try {
|
try {
|
||||||
fileBytes = cz.kamma.kfmanager.service.FileOperations.readFileFromArchive(file, virtualPath);
|
fileBytes = cz.kamma.kfmanager.service.FileOperations.readFileFromArchive(file, virtualPath);
|
||||||
boolean binary = isBinary(fileBytes);
|
boolean binary = isBinary(fileBytes);
|
||||||
@ -1212,6 +1227,40 @@ public class FileEditor extends JFrame {
|
|||||||
Files.write(file.toPath(), content.getBytes("UTF-8"));
|
Files.write(file.toPath(), content.getBytes("UTF-8"));
|
||||||
modified = false;
|
modified = false;
|
||||||
updateTitle();
|
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,
|
JOptionPane.showMessageDialog(this,
|
||||||
"File saved",
|
"File saved",
|
||||||
"Success",
|
"Success",
|
||||||
|
|||||||
@ -440,6 +440,8 @@ public class FilePanel extends JPanel {
|
|||||||
// Extract display name from FTP URL for tab title
|
// Extract display name from FTP URL for tab title
|
||||||
String tabTitle = getFtpTabTitle(ftpPath);
|
String tabTitle = getFtpTabTitle(ftpPath);
|
||||||
|
|
||||||
|
applyConfiguredAppearance(tab);
|
||||||
|
|
||||||
tabbedPane.addTab(tabTitle, tab);
|
tabbedPane.addTab(tabTitle, tab);
|
||||||
tabbedPane.setSelectedComponent(tab);
|
tabbedPane.setSelectedComponent(tab);
|
||||||
|
|
||||||
@ -788,9 +790,11 @@ public class FilePanel extends JPanel {
|
|||||||
Component comp = tabbedPane.getComponentAt(i);
|
Component comp = tabbedPane.getComponentAt(i);
|
||||||
if (comp instanceof FilePanelTab tab && tab.isFtpTab()) {
|
if (comp instanceof FilePanelTab tab && tab.isFtpTab()) {
|
||||||
if (tabbedPane.getTabCount() > 1) {
|
if (tabbedPane.getTabCount() > 1) {
|
||||||
|
tab.cleanup();
|
||||||
tabbedPane.removeTabAt(i);
|
tabbedPane.removeTabAt(i);
|
||||||
} else {
|
} else {
|
||||||
// If it's the last tab, reset it to home instead of removing
|
// If it's the last tab, reset it to home instead of removing
|
||||||
|
tab.cleanup();
|
||||||
tab.loadDirectory(new File(System.getProperty("user.home")));
|
tab.loadDirectory(new File(System.getProperty("user.home")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -805,6 +809,10 @@ public class FilePanel extends JPanel {
|
|||||||
public void closeCurrentTab() {
|
public void closeCurrentTab() {
|
||||||
int index = tabbedPane.getSelectedIndex();
|
int index = tabbedPane.getSelectedIndex();
|
||||||
if (index >= 0 && tabbedPane.getTabCount() > 1) {
|
if (index >= 0 && tabbedPane.getTabCount() > 1) {
|
||||||
|
Component comp = tabbedPane.getComponentAt(index);
|
||||||
|
if (comp instanceof FilePanelTab tab) {
|
||||||
|
tab.cleanup();
|
||||||
|
}
|
||||||
tabbedPane.removeTabAt(index);
|
tabbedPane.removeTabAt(index);
|
||||||
updatePathField();
|
updatePathField();
|
||||||
updateTabStyles();
|
updateTabStyles();
|
||||||
|
|||||||
@ -1624,8 +1624,14 @@ public class FilePanelTab extends JPanel {
|
|||||||
*/
|
*/
|
||||||
public void navigateFtpUp() {
|
public void navigateFtpUp() {
|
||||||
if (ftpCurrentPath == null || ftpCurrentPath.equals("/")) return;
|
if (ftpCurrentPath == null || ftpCurrentPath.equals("/")) return;
|
||||||
|
String previousDirName = FtpService.getNameFromPath(ftpCurrentPath);
|
||||||
String parentPath = FtpService.getParentPath(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();
|
updateSortButtonsDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleanup resources when tab is closed.
|
||||||
|
*/
|
||||||
|
public void cleanup() {
|
||||||
|
if (isFtpTab && ftpProfile != null) {
|
||||||
|
FtpService.disconnect(ftpProfile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
public JTable getFileTable() {
|
public JTable getFileTable() {
|
||||||
return fileTable;
|
return fileTable;
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import java.awt.datatransfer.DataFlavor;
|
|||||||
import java.awt.event.*;
|
import java.awt.event.*;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -2437,6 +2438,30 @@ public class MainWindow extends JFrame {
|
|||||||
return;
|
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();
|
File file = item.getFile();
|
||||||
|
|
||||||
// Removed previous 10 MB limit: allow opening large files in the viewer (paged hex will stream large binaries).
|
// 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;
|
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();
|
File file = item.getFile();
|
||||||
|
|
||||||
// If an external editor is configured, try launching it with the file path
|
// 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();
|
MainWindow.this.toFront();
|
||||||
|
|
||||||
for (FilePanel panel : panelsToRefresh) {
|
for (FilePanel panel : panelsToRefresh) {
|
||||||
if (panel.getCurrentDirectory() != null) {
|
panel.refresh(false);
|
||||||
panel.loadDirectory(panel.getCurrentDirectory(), false, false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (postTask != null) {
|
if (postTask != null) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user