IND$FILE Browse!
This commit is contained in:
@@ -78,7 +78,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
if (client != null) {
|
||||
statusBar.updateStatus();
|
||||
syncModeMenuItems();
|
||||
if (!isAnyChildDialogActive() && KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow() == this) {
|
||||
if (!isModalDialogActive() && KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow() == this) {
|
||||
if (!terminalPanel.hasFocus()) {
|
||||
terminalPanel.requestFocusInWindow();
|
||||
}
|
||||
@@ -87,6 +87,8 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
});
|
||||
refreshTimer.start();
|
||||
|
||||
setAutoRequestFocus(true);
|
||||
|
||||
addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing(WindowEvent e) {
|
||||
@@ -104,8 +106,17 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
|
||||
@Override
|
||||
public void windowActivated(WindowEvent e) {
|
||||
if (!isAnyChildDialogActive()) {
|
||||
terminalPanel.requestFocusInWindow();
|
||||
if (!isModalDialogActive() && terminalPanel != null) {
|
||||
terminalPanel.requestFocus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
addWindowFocusListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowGainedFocus(WindowEvent e) {
|
||||
if (!isModalDialogActive() && terminalPanel != null) {
|
||||
terminalPanel.requestFocus();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -117,16 +128,20 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
Object src = me.getSource();
|
||||
if (src instanceof Component) {
|
||||
Window win = SwingUtilities.getWindowAncestor((Component) src);
|
||||
if (win == J3270App.this && !isAnyChildDialogActive()) {
|
||||
if (win == J3270App.this && !isModalDialogActive()) {
|
||||
if (!J3270App.this.isFocused() || !J3270App.this.isActive()) {
|
||||
J3270App.this.toFront();
|
||||
J3270App.this.requestFocus();
|
||||
}
|
||||
boolean isMenu = (src instanceof JMenuBar) || (src instanceof JMenuItem) || (src instanceof JPopupMenu)
|
||||
|| SwingUtilities.getAncestorOfClass(JMenuBar.class, (Component) src) != null
|
||||
|| SwingUtilities.getAncestorOfClass(JPopupMenu.class, (Component) src) != null;
|
||||
if (!isMenu && terminalPanel != null && !terminalPanel.hasFocus()) {
|
||||
terminalPanel.requestFocusInWindow();
|
||||
terminalPanel.requestFocus();
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (terminalPanel != null && !terminalPanel.hasFocus()) {
|
||||
terminalPanel.requestFocus();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,19 +149,9 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
}, AWTEvent.MOUSE_EVENT_MASK);
|
||||
}
|
||||
|
||||
private boolean isAnyChildDialogActive() {
|
||||
Window focusedWindow = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
|
||||
if (focusedWindow != null && focusedWindow != this) {
|
||||
return true;
|
||||
}
|
||||
if (findDialog != null && findDialog.isVisible()) return true;
|
||||
if (scriptDialog != null && scriptDialog.isVisible()) return true;
|
||||
if (macroDialog != null && macroDialog.isVisible()) return true;
|
||||
if (fieldInspectorDialog != null && fieldInspectorDialog.isVisible()) return true;
|
||||
if (printerSessionDialog != null && printerSessionDialog.isVisible()) return true;
|
||||
if (screenHistoryDialog != null && screenHistoryDialog.isVisible()) return true;
|
||||
private boolean isModalDialogActive() {
|
||||
for (Window owned : getOwnedWindows()) {
|
||||
if (owned != null && owned.isVisible()) {
|
||||
if (owned instanceof Dialog && owned.isVisible() && ((Dialog) owned).isModal()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ public class FileTransferDialog extends JDialog {
|
||||
public String lrecl = "";
|
||||
public String blksize = "";
|
||||
public String space = "";
|
||||
public FTConfig.AllocationUnit units = FTConfig.AllocationUnit.TRACKS;
|
||||
public String dirBlocks = "";
|
||||
public String options = "";
|
||||
}
|
||||
|
||||
@@ -82,6 +84,8 @@ public class FileTransferDialog extends JDialog {
|
||||
private JTextField lreclField;
|
||||
private JTextField blksizeField;
|
||||
private JTextField spaceField;
|
||||
private JComboBox<FTConfig.AllocationUnit> unitsCombo;
|
||||
private JTextField dirField;
|
||||
|
||||
// VM specific
|
||||
private JTextField optionsField;
|
||||
@@ -220,7 +224,7 @@ public class FileTransferDialog extends JDialog {
|
||||
formPanel.add(createRow("File Options:", optionsPanel2));
|
||||
|
||||
// Host-specific options panel (TSO dataset allocation)
|
||||
JPanel hostOptsPanel = new JPanel(new GridLayout(2, 4, 5, 5));
|
||||
JPanel hostOptsPanel = new JPanel(new GridLayout(3, 4, 5, 5));
|
||||
hostOptsPanel.setOpaque(false);
|
||||
hostOptsPanel.setBorder(ThemeManager.createTitledBorder("TSO Allocation Options (Send Only)"));
|
||||
|
||||
@@ -236,10 +240,24 @@ public class FileTransferDialog extends JDialog {
|
||||
blksizeField = new JTextField(5);
|
||||
hostOptsPanel.add(blksizeField);
|
||||
|
||||
hostOptsPanel.add(new JLabel("SPACE:"));
|
||||
hostOptsPanel.add(new JLabel("Units:"));
|
||||
unitsCombo = new JComboBox<>(new FTConfig.AllocationUnit[]{
|
||||
FTConfig.AllocationUnit.TRACKS,
|
||||
FTConfig.AllocationUnit.CYLINDERS,
|
||||
FTConfig.AllocationUnit.AVBLOCK,
|
||||
FTConfig.AllocationUnit.DEFAULT
|
||||
});
|
||||
unitsCombo.setSelectedItem(FTConfig.AllocationUnit.TRACKS);
|
||||
hostOptsPanel.add(unitsCombo);
|
||||
|
||||
hostOptsPanel.add(new JLabel("SPACE (Pri,Sec):"));
|
||||
spaceField = new JTextField(15);
|
||||
hostOptsPanel.add(spaceField);
|
||||
|
||||
hostOptsPanel.add(new JLabel("Directory (PDS):"));
|
||||
dirField = new JTextField(5);
|
||||
hostOptsPanel.add(dirField);
|
||||
|
||||
formPanel.add(Box.createRigidArea(new Dimension(0, 10)));
|
||||
formPanel.add(hostOptsPanel);
|
||||
|
||||
@@ -306,7 +324,9 @@ public class FileTransferDialog extends JDialog {
|
||||
if (lastTransferState.recfm != null) recfmField.setText(lastTransferState.recfm);
|
||||
if (lastTransferState.lrecl != null) lreclField.setText(lastTransferState.lrecl);
|
||||
if (lastTransferState.blksize != null) blksizeField.setText(lastTransferState.blksize);
|
||||
if (lastTransferState.units != null) unitsCombo.setSelectedItem(lastTransferState.units);
|
||||
if (lastTransferState.space != null) spaceField.setText(lastTransferState.space);
|
||||
if (lastTransferState.dirBlocks != null) dirField.setText(lastTransferState.dirBlocks);
|
||||
if (lastTransferState.options != null) optionsField.setText(lastTransferState.options);
|
||||
}
|
||||
}
|
||||
@@ -327,7 +347,9 @@ public class FileTransferDialog extends JDialog {
|
||||
recfmField.setEnabled(isSend && isTso);
|
||||
lreclField.setEnabled(isSend && isTso);
|
||||
blksizeField.setEnabled(isSend && isTso);
|
||||
unitsCombo.setEnabled(isSend && isTso);
|
||||
spaceField.setEnabled(isSend && isTso);
|
||||
dirField.setEnabled(isSend && isTso);
|
||||
optionsField.setEnabled(isCms);
|
||||
}
|
||||
|
||||
@@ -410,7 +432,10 @@ public class FileTransferDialog extends JDialog {
|
||||
config.setRecfm(recfmField.getText().trim());
|
||||
config.setLrecl(lreclField.getText().trim());
|
||||
config.setBlksize(blksizeField.getText().trim());
|
||||
FTConfig.AllocationUnit selectedUnits = (FTConfig.AllocationUnit) unitsCombo.getSelectedItem();
|
||||
config.setUnits(selectedUnits != null ? selectedUnits : FTConfig.AllocationUnit.DEFAULT);
|
||||
config.setSpace(spaceField.getText().trim());
|
||||
config.setDirectoryBlocks(dirField.getText().trim());
|
||||
config.setOptions(optionsField.getText().trim());
|
||||
|
||||
recordSessionTransfer();
|
||||
@@ -439,7 +464,9 @@ public class FileTransferDialog extends JDialog {
|
||||
state.recfm = recfmField.getText().trim();
|
||||
state.lrecl = lreclField.getText().trim();
|
||||
state.blksize = blksizeField.getText().trim();
|
||||
state.units = (FTConfig.AllocationUnit) unitsCombo.getSelectedItem();
|
||||
state.space = spaceField.getText().trim();
|
||||
state.dirBlocks = dirField.getText().trim();
|
||||
state.options = optionsField.getText().trim();
|
||||
lastTransferState = state;
|
||||
}
|
||||
@@ -459,7 +486,9 @@ public class FileTransferDialog extends JDialog {
|
||||
public JTextField getRecfmField() { return recfmField; }
|
||||
public JTextField getLreclField() { return lreclField; }
|
||||
public JTextField getBlksizeField() { return blksizeField; }
|
||||
public JComboBox<FTConfig.AllocationUnit> getUnitsCombo() { return unitsCombo; }
|
||||
public JTextField getSpaceField() { return spaceField; }
|
||||
public JTextField getDirField() { return dirField; }
|
||||
public JTextField getOptionsField() { return optionsField; }
|
||||
|
||||
private void applyTheme(Container container) {
|
||||
|
||||
@@ -3,15 +3,19 @@ package haus.nightmare.j3270.ui;
|
||||
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||
import haus.nightmare.lib3270j.ft.FTConfig;
|
||||
import haus.nightmare.lib3270j.ft.dir.CMSDirectoryEntry;
|
||||
import haus.nightmare.lib3270j.ft.dir.CMSDirectoryParser;
|
||||
import haus.nightmare.lib3270j.ft.dir.HostDirectoryEntry;
|
||||
import haus.nightmare.lib3270j.ft.dir.TSODirectoryEntry;
|
||||
import haus.nightmare.lib3270j.ft.dir.TSODirectoryParser;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import javax.swing.border.LineBorder;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import javax.swing.table.JTableHeader;
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.DataFlavor;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.ArrayList;
|
||||
@@ -19,6 +23,7 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* Host Directory and Dataset Catalog browser dialog for TSO and VM/CMS environments.
|
||||
* Aligned with IBM Host On-Demand presentation space queries and clipboard parsing.
|
||||
*/
|
||||
public class HostDirectoryDialog extends JDialog {
|
||||
|
||||
@@ -27,6 +32,7 @@ public class HostDirectoryDialog extends JDialog {
|
||||
|
||||
private JComboBox<FTConfig.HostType> hostTypeCombo;
|
||||
private JTextField queryField;
|
||||
private JButton queryHostBtn;
|
||||
private JTable table;
|
||||
private DefaultTableModel tableModel;
|
||||
private JLabel statusLabel;
|
||||
@@ -34,6 +40,7 @@ public class HostDirectoryDialog extends JDialog {
|
||||
private String selectedHostFile = null;
|
||||
private boolean confirmed = false;
|
||||
|
||||
private final List<HostDirectoryEntry> allEntries = new ArrayList<>();
|
||||
private final List<HostDirectoryEntry> currentEntries = new ArrayList<>();
|
||||
|
||||
public HostDirectoryDialog(Dialog parent, Telnet3270Client client, FTConfig.HostType hostType, String initialQuery) {
|
||||
@@ -43,7 +50,7 @@ public class HostDirectoryDialog extends JDialog {
|
||||
AppIcon.applyTo(this);
|
||||
|
||||
buildUI(initialQuery);
|
||||
setSize(700, 420);
|
||||
setSize(780, 460);
|
||||
setLocationRelativeTo(parent);
|
||||
}
|
||||
|
||||
@@ -69,10 +76,18 @@ public class HostDirectoryDialog extends JDialog {
|
||||
if (initialHostType == FTConfig.HostType.CMS) hostTypeCombo.setSelectedItem(FTConfig.HostType.CMS);
|
||||
else hostTypeCombo.setSelectedItem(FTConfig.HostType.TSO);
|
||||
ThemeManager.styleComboBox(hostTypeCombo);
|
||||
hostTypeCombo.addActionListener(e -> {
|
||||
setupTableColumns();
|
||||
if (client != null && client.getConnectionState().isFullSession()) {
|
||||
queryHost();
|
||||
} else {
|
||||
parseCurrentScreen();
|
||||
}
|
||||
});
|
||||
topPanel.add(hostTypeCombo, gbc);
|
||||
|
||||
gbc.gridx = 2;
|
||||
JLabel queryLabel = new JLabel("Query Pattern / Text:");
|
||||
JLabel queryLabel = new JLabel("Filter / Pattern:");
|
||||
queryLabel.setFont(font);
|
||||
topPanel.add(queryLabel, gbc);
|
||||
|
||||
@@ -81,15 +96,24 @@ public class HostDirectoryDialog extends JDialog {
|
||||
queryField = new JTextField(initialQuery != null ? initialQuery : "", 16);
|
||||
queryField.setFont(ThemeManager.getMonospacedUiFont());
|
||||
ThemeManager.styleTextField(queryField);
|
||||
queryField.addActionListener(e -> onFilterOrParse());
|
||||
topPanel.add(queryField, gbc);
|
||||
|
||||
gbc.gridx = 4;
|
||||
gbc.weightx = 0;
|
||||
JButton parseBtn = new JButton("Query / Parse");
|
||||
ThemeManager.styleButton(parseBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||
parseBtn.setFont(font);
|
||||
parseBtn.addActionListener(e -> runQuery());
|
||||
topPanel.add(parseBtn, gbc);
|
||||
JButton filterBtn = new JButton("Filter / Parse");
|
||||
ThemeManager.styleButton(filterBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||
filterBtn.setFont(font);
|
||||
filterBtn.addActionListener(e -> onFilterOrParse());
|
||||
topPanel.add(filterBtn, gbc);
|
||||
|
||||
gbc.gridx = 5;
|
||||
queryHostBtn = new JButton("Query Host");
|
||||
ThemeManager.styleButton(queryHostBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||
queryHostBtn.setFont(font);
|
||||
queryHostBtn.setToolTipText("Send LISTCAT or LISTFILE query to active host session");
|
||||
queryHostBtn.addActionListener(e -> queryHost());
|
||||
topPanel.add(queryHostBtn, gbc);
|
||||
|
||||
mainPanel.add(topPanel, BorderLayout.NORTH);
|
||||
|
||||
@@ -129,11 +153,18 @@ public class HostDirectoryDialog extends JDialog {
|
||||
JPanel btnPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 6, 0));
|
||||
btnPanel.setOpaque(false);
|
||||
|
||||
JButton pasteScreenBtn = new JButton("Parse Current Screen");
|
||||
JButton pasteScreenBtn = new JButton("Parse Screen");
|
||||
ThemeManager.styleButton(pasteScreenBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||
pasteScreenBtn.setFont(font);
|
||||
pasteScreenBtn.setToolTipText("Parse items currently visible on the 3270 presentation space");
|
||||
pasteScreenBtn.addActionListener(e -> parseCurrentScreen());
|
||||
|
||||
JButton pasteClipBtn = new JButton("Paste Clipboard");
|
||||
ThemeManager.styleButton(pasteClipBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||
pasteClipBtn.setFont(font);
|
||||
pasteClipBtn.setToolTipText("Parse host filenames copied to the system clipboard (HoD compatible)");
|
||||
pasteClipBtn.addActionListener(e -> parseClipboard());
|
||||
|
||||
JButton selectBtn = new JButton("Select Dataset");
|
||||
ThemeManager.styleButton(selectBtn, ThemeManager.ButtonVariant.PRIMARY);
|
||||
selectBtn.setFont(font);
|
||||
@@ -148,6 +179,7 @@ public class HostDirectoryDialog extends JDialog {
|
||||
});
|
||||
|
||||
btnPanel.add(pasteScreenBtn);
|
||||
btnPanel.add(pasteClipBtn);
|
||||
btnPanel.add(Box.createHorizontalStrut(10));
|
||||
btnPanel.add(selectBtn);
|
||||
btnPanel.add(cancelBtn);
|
||||
@@ -158,12 +190,18 @@ public class HostDirectoryDialog extends JDialog {
|
||||
setContentPane(mainPanel);
|
||||
ThemeManager.applyThemeToWindow(this);
|
||||
|
||||
// Initial setup
|
||||
// Initial setup: columns and screen parse or host query
|
||||
setupTableColumns();
|
||||
if (initialQuery != null && !initialQuery.isEmpty()) {
|
||||
runQuery();
|
||||
if (initialQuery != null && !initialQuery.trim().isEmpty()) {
|
||||
queryField.setText(initialQuery.trim());
|
||||
}
|
||||
if (client != null && client.getConnectionState().isFullSession()) {
|
||||
queryHost();
|
||||
} else {
|
||||
parseCurrentScreen();
|
||||
if (initialQuery != null && !initialQuery.trim().isEmpty()) {
|
||||
applyFilter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,69 +219,241 @@ public class HostDirectoryDialog extends JDialog {
|
||||
}
|
||||
}
|
||||
|
||||
private void runQuery() {
|
||||
setupTableColumns();
|
||||
currentEntries.clear();
|
||||
private void onFilterOrParse() {
|
||||
String query = queryField.getText().trim();
|
||||
FTConfig.HostType hostType = (FTConfig.HostType) hostTypeCombo.getSelectedItem();
|
||||
// If text contains multiple lines, treat it as pasted raw text to parse
|
||||
if (query.contains("\n") || query.contains("\r") || query.toUpperCase().contains("NONVSAM")
|
||||
|| query.toUpperCase().contains("IN-CAT") || query.toUpperCase().contains("FILENAME")) {
|
||||
parseText(query, "query text");
|
||||
} else if (allEntries.isEmpty() && client != null && client.getConnectionState().isFullSession()) {
|
||||
queryHost();
|
||||
} else {
|
||||
applyFilter();
|
||||
}
|
||||
}
|
||||
|
||||
if (client != null && client.getXfer() != null) {
|
||||
if (hostType == FTConfig.HostType.CMS) {
|
||||
List<CMSDirectoryEntry> cmsEntries = client.getXfer().getCmsDirectory(query);
|
||||
currentEntries.addAll(cmsEntries);
|
||||
for (CMSDirectoryEntry entry : cmsEntries) {
|
||||
tableModel.addRow(new Object[]{
|
||||
entry.getFilename(), entry.getFiletype(), entry.getFilemode(),
|
||||
entry.getRecfm(), entry.getLrecl(), entry.getNumRecords(), entry.getDate() + " " + entry.getTime()
|
||||
});
|
||||
}
|
||||
} else {
|
||||
List<TSODirectoryEntry> tsoEntries = client.getXfer().getTsoDirectory(query);
|
||||
currentEntries.addAll(tsoEntries);
|
||||
for (TSODirectoryEntry entry : tsoEntries) {
|
||||
tableModel.addRow(new Object[]{
|
||||
entry.getDatasetName(), entry.getVolume(), entry.getRecfm(),
|
||||
entry.getLrecl(), entry.getBlksize(), entry.getTracksAllocated(), entry.getExtents()
|
||||
});
|
||||
}
|
||||
}
|
||||
private void queryHost() {
|
||||
if (client == null || !client.getConnectionState().isFullSession()) {
|
||||
statusLabel.setText("No active session to query.");
|
||||
return;
|
||||
}
|
||||
|
||||
statusLabel.setText(currentEntries.size() + " entries found.");
|
||||
String pattern = queryField.getText().trim();
|
||||
FTConfig.HostType hostType = (FTConfig.HostType) hostTypeCombo.getSelectedItem();
|
||||
statusLabel.setText("Querying host (" + hostType + ")...");
|
||||
if (queryHostBtn != null) queryHostBtn.setEnabled(false);
|
||||
|
||||
SwingWorker<String, Void> worker = new SwingWorker<>() {
|
||||
@Override
|
||||
protected String doInBackground() throws Exception {
|
||||
if (client.getPS() == null) return "";
|
||||
|
||||
// 1. Clear screen and wait for host to unlock keyboard
|
||||
client.getPS().SendKeys("[clear]");
|
||||
waitForKeyboardUnlock(client, 3000);
|
||||
|
||||
// 2. Format query command
|
||||
if (hostType == FTConfig.HostType.CMS) {
|
||||
String cmsFilter;
|
||||
if (pattern.isEmpty()) {
|
||||
cmsFilter = "* * *";
|
||||
} else {
|
||||
String clean = pattern.replace("'", "").replace("\"", "").trim();
|
||||
if (clean.contains(".")) {
|
||||
int dot = clean.indexOf('.');
|
||||
cmsFilter = clean.substring(0, dot).trim() + " " + clean.substring(dot + 1).trim() + " *";
|
||||
} else if (clean.split("\\s+").length == 1) {
|
||||
cmsFilter = clean + " * *";
|
||||
} else {
|
||||
cmsFilter = clean;
|
||||
}
|
||||
}
|
||||
client.getPS().SendKeys("LISTFILE " + cmsFilter + " ( nohead alloc date[enter]");
|
||||
} else {
|
||||
if (pattern.isEmpty()) {
|
||||
client.getPS().SendKeys("LISTCAT[enter]");
|
||||
} else {
|
||||
String dsnPattern = pattern.trim();
|
||||
if (dsnPattern.startsWith("'") && dsnPattern.endsWith("'") && dsnPattern.length() > 2) {
|
||||
dsnPattern = dsnPattern.substring(1, dsnPattern.length() - 1);
|
||||
}
|
||||
int paren = dsnPattern.indexOf('(');
|
||||
if (paren > 0) {
|
||||
dsnPattern = dsnPattern.substring(0, paren);
|
||||
}
|
||||
client.getPS().SendKeys("LISTCAT LEVEL(" + dsnPattern + ")[enter]");
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Wait for host to process command and unlock keyboard
|
||||
Thread.sleep(100);
|
||||
waitForKeyboardUnlock(client, 5000);
|
||||
|
||||
// 4. Collect pages, handling MORE... on CMS or *** on TSO
|
||||
StringBuilder allPages = new StringBuilder();
|
||||
int maxPages = 20;
|
||||
for (int page = 0; page < maxPages; page++) {
|
||||
int rows = client.getPS().getRows();
|
||||
int cols = client.getPS().getCols();
|
||||
String screen = client.getPS().copyString(0, 0, rows - 1, cols - 1);
|
||||
allPages.append(screen).append("\n");
|
||||
|
||||
boolean hasMore = false;
|
||||
if (hostType == FTConfig.HostType.CMS) {
|
||||
if (screen.contains("MORE...") || screen.contains("HOLDING")) {
|
||||
hasMore = true;
|
||||
client.getPS().SendKeys("[enter]");
|
||||
}
|
||||
} else {
|
||||
if (screen.contains("***")) {
|
||||
hasMore = true;
|
||||
client.getPS().SendKeys("[enter]");
|
||||
}
|
||||
}
|
||||
|
||||
if (hasMore) {
|
||||
Thread.sleep(100);
|
||||
waitForKeyboardUnlock(client, 3000);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return allPages.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
if (queryHostBtn != null) queryHostBtn.setEnabled(true);
|
||||
try {
|
||||
String screenText = get();
|
||||
parseText(screenText, "host query");
|
||||
if (!pattern.isEmpty() && !allEntries.isEmpty()) {
|
||||
applyFilter();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
statusLabel.setText("Host query failed: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
};
|
||||
worker.execute();
|
||||
}
|
||||
|
||||
private static boolean waitForKeyboardUnlock(Telnet3270Client client, long timeoutMs) {
|
||||
if (client == null) return false;
|
||||
long deadline = System.currentTimeMillis() + timeoutMs;
|
||||
while (System.currentTimeMillis() < deadline) {
|
||||
boolean locked = false;
|
||||
if (client.getInputProcessor() != null && client.getInputProcessor().isKeyboardLocked()) {
|
||||
locked = true;
|
||||
}
|
||||
if (client.getOIA() != null && (client.getOIA().isKeyLocked() || client.getOIA().isXSystem())) {
|
||||
locked = true;
|
||||
}
|
||||
if (!locked) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(40);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void parseCurrentScreen() {
|
||||
if (client == null || !client.getConnectionState().isFullSession()) {
|
||||
if (client == null || !client.getConnectionState().isFullSession() || client.getPS() == null) {
|
||||
statusLabel.setText("No active session to parse.");
|
||||
return;
|
||||
}
|
||||
|
||||
int rows = client.getPS().getRows();
|
||||
int cols = client.getPS().getCols();
|
||||
String screenText = client.getPS().copyString(0, 0, rows - 1, cols - 1);
|
||||
parseText(screenText, "presentation space");
|
||||
}
|
||||
|
||||
private void parseClipboard() {
|
||||
try {
|
||||
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||
Transferable contents = clipboard.getContents(null);
|
||||
if (contents != null && contents.isDataFlavorSupported(DataFlavor.stringFlavor)) {
|
||||
String clipText = (String) contents.getTransferData(DataFlavor.stringFlavor);
|
||||
if (clipText != null && !clipText.trim().isEmpty()) {
|
||||
parseText(clipText, "clipboard");
|
||||
return;
|
||||
}
|
||||
}
|
||||
statusLabel.setText("Clipboard contains no text.");
|
||||
} catch (Exception ex) {
|
||||
statusLabel.setText("Error reading clipboard: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void parseText(String text, String source) {
|
||||
setupTableColumns();
|
||||
currentEntries.clear();
|
||||
allEntries.clear();
|
||||
FTConfig.HostType hostType = (FTConfig.HostType) hostTypeCombo.getSelectedItem();
|
||||
String screenText = client.getPS().getString(0, client.getPS().getSize());
|
||||
|
||||
if (hostType == FTConfig.HostType.CMS) {
|
||||
List<CMSDirectoryEntry> cmsEntries = client.getXfer().getCmsDirectory(screenText);
|
||||
currentEntries.addAll(cmsEntries);
|
||||
for (CMSDirectoryEntry entry : cmsEntries) {
|
||||
tableModel.addRow(new Object[]{
|
||||
entry.getFilename(), entry.getFiletype(), entry.getFilemode(),
|
||||
entry.getRecfm(), entry.getLrecl(), entry.getNumRecords(), entry.getDate() + " " + entry.getTime()
|
||||
});
|
||||
}
|
||||
List<CMSDirectoryEntry> cmsEntries = CMSDirectoryParser.parse(text);
|
||||
allEntries.addAll(cmsEntries);
|
||||
} else {
|
||||
List<TSODirectoryEntry> tsoEntries = client.getXfer().getTsoDirectory(screenText);
|
||||
currentEntries.addAll(tsoEntries);
|
||||
for (TSODirectoryEntry entry : tsoEntries) {
|
||||
List<TSODirectoryEntry> tsoEntries = TSODirectoryParser.parse(text);
|
||||
allEntries.addAll(tsoEntries);
|
||||
}
|
||||
|
||||
applyFilter();
|
||||
if (allEntries.isEmpty()) {
|
||||
statusLabel.setText("0 entries found from " + source + ". Use 'Query Host' or 'Paste Clipboard'.");
|
||||
} else {
|
||||
statusLabel.setText("Parsed " + allEntries.size() + " entries from " + source + ".");
|
||||
}
|
||||
}
|
||||
|
||||
private void applyFilter() {
|
||||
tableModel.setRowCount(0);
|
||||
currentEntries.clear();
|
||||
String filter = queryField.getText().trim();
|
||||
String filterUpper = filter.toUpperCase();
|
||||
FTConfig.HostType hostType = (FTConfig.HostType) hostTypeCombo.getSelectedItem();
|
||||
|
||||
for (HostDirectoryEntry entry : allEntries) {
|
||||
if (!filter.isEmpty() && !matchesFilter(entry.getName().toUpperCase(), filterUpper)) {
|
||||
continue;
|
||||
}
|
||||
currentEntries.add(entry);
|
||||
if (hostType == FTConfig.HostType.CMS && entry instanceof CMSDirectoryEntry) {
|
||||
CMSDirectoryEntry cms = (CMSDirectoryEntry) entry;
|
||||
tableModel.addRow(new Object[]{
|
||||
entry.getDatasetName(), entry.getVolume(), entry.getRecfm(),
|
||||
entry.getLrecl(), entry.getBlksize(), entry.getTracksAllocated(), entry.getExtents()
|
||||
cms.getFilename(), cms.getFiletype(), cms.getFilemode(),
|
||||
cms.getRecfm(), cms.getLrecl(), cms.getNumRecords(), cms.getDate() + " " + cms.getTime()
|
||||
});
|
||||
} else if (entry instanceof TSODirectoryEntry) {
|
||||
TSODirectoryEntry tso = (TSODirectoryEntry) entry;
|
||||
tableModel.addRow(new Object[]{
|
||||
tso.getDatasetName(), tso.getVolume(), tso.getRecfm(),
|
||||
tso.getLrecl(), tso.getBlksize(), tso.getTracksAllocated(), tso.getExtents()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
statusLabel.setText("Parsed " + currentEntries.size() + " entries from presentation space.");
|
||||
if (!allEntries.isEmpty()) {
|
||||
statusLabel.setText(currentEntries.size() + " entries shown (" + allEntries.size() + " total).");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean matchesFilter(String name, String filter) {
|
||||
String cleanFilter = filter.replace("'", "").trim();
|
||||
if (cleanFilter.isEmpty() || cleanFilter.equals("*") || cleanFilter.equals("* * *") || cleanFilter.equals("*.*")) {
|
||||
return true;
|
||||
}
|
||||
if (cleanFilter.contains("*")) {
|
||||
String regex = "\\Q" + cleanFilter.replace("*", "\\E.*\\Q") + "\\E";
|
||||
return name.matches(regex);
|
||||
}
|
||||
return name.contains(cleanFilter);
|
||||
}
|
||||
|
||||
private void onConfirmSelection() {
|
||||
@@ -267,4 +477,12 @@ public class HostDirectoryDialog extends JDialog {
|
||||
public String getSelectedHostFile() {
|
||||
return selectedHostFile;
|
||||
}
|
||||
|
||||
public List<HostDirectoryEntry> getAllEntries() {
|
||||
return allEntries;
|
||||
}
|
||||
|
||||
public List<HostDirectoryEntry> getCurrentEntries() {
|
||||
return currentEntries;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,11 +236,15 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable, I
|
||||
boolean wasPanelUnfocused = !hasFocus();
|
||||
isActivationClick = wasWindowInactive || wasPanelUnfocused;
|
||||
|
||||
if (wasWindowInactive) {
|
||||
if (win != null && wasWindowInactive) {
|
||||
win.toFront();
|
||||
win.requestFocus();
|
||||
}
|
||||
requestFocus();
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (!hasFocus()) {
|
||||
requestFocus();
|
||||
}
|
||||
});
|
||||
|
||||
// Right-click context menu
|
||||
if (SwingUtilities.isRightMouseButton(e)) {
|
||||
@@ -433,6 +437,9 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable, I
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
if (!hasFocus()) {
|
||||
requestFocus();
|
||||
}
|
||||
if (isDragging) {
|
||||
isDragging = false;
|
||||
if (activeDragHandle != DragHandle.NONE) {
|
||||
|
||||
@@ -99,4 +99,45 @@ public class FileTransferSessionAndCmsTest {
|
||||
// Headless environment; verified through state retention
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPdsSessionStatePersistence() {
|
||||
FileTransferDialog.TransferSessionState state = new FileTransferDialog.TransferSessionState();
|
||||
state.hostType = FTConfig.HostType.TSO;
|
||||
state.isSend = true;
|
||||
state.hostFile = "'USER.PDS.DATA'";
|
||||
state.units = FTConfig.AllocationUnit.CYLINDERS;
|
||||
state.space = "10,5";
|
||||
state.dirBlocks = "20";
|
||||
|
||||
FileTransferDialog.setLastTransferState(state);
|
||||
|
||||
FileTransferDialog.TransferSessionState retrieved = FileTransferDialog.getLastTransferState();
|
||||
assertNotNull(retrieved);
|
||||
assertEquals(FTConfig.AllocationUnit.CYLINDERS, retrieved.units);
|
||||
assertEquals("10,5", retrieved.space);
|
||||
assertEquals("20", retrieved.dirBlocks);
|
||||
|
||||
try {
|
||||
FileTransferDialog dialog = new FileTransferDialog(null, null);
|
||||
assertEquals(FTConfig.AllocationUnit.CYLINDERS, dialog.getUnitsCombo().getSelectedItem());
|
||||
assertEquals("10,5", dialog.getSpaceField().getText());
|
||||
assertEquals("20", dialog.getDirField().getText());
|
||||
dialog.dispose();
|
||||
} catch (HeadlessException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHostDirectoryDialogStructureAndFilter() {
|
||||
try {
|
||||
haus.nightmare.j3270.ui.HostDirectoryDialog dialog =
|
||||
new haus.nightmare.j3270.ui.HostDirectoryDialog(null, null, FTConfig.HostType.CMS, "TEST EXEC");
|
||||
assertNotNull(dialog);
|
||||
assertFalse(dialog.isConfirmed());
|
||||
assertNull(dialog.getSelectedHostFile());
|
||||
dialog.dispose();
|
||||
} catch (HeadlessException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,4 +98,25 @@ public class TerminalPanelSelectionTest {
|
||||
// Handled gracefully in headless environments
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Mouse press and release on TerminalPanel requests focus")
|
||||
public void testTerminalPanelMousePressRequestsFocus() {
|
||||
try {
|
||||
TerminalPanel panel = new TerminalPanel();
|
||||
assertTrue(panel.isFocusable());
|
||||
|
||||
MouseListener[] mouseListeners = panel.getMouseListeners();
|
||||
assertTrue(mouseListeners.length > 0);
|
||||
|
||||
MouseEvent pressEvent = new MouseEvent(panel, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(),
|
||||
0, 50, 50, 1, false, MouseEvent.BUTTON1);
|
||||
mouseListeners[0].mousePressed(pressEvent);
|
||||
|
||||
MouseEvent releaseEvent = new MouseEvent(panel, MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(),
|
||||
0, 50, 50, 1, false, MouseEvent.BUTTON1);
|
||||
mouseListeners[0].mouseReleased(releaseEvent);
|
||||
} catch (HeadlessException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,11 @@ import java.util.regex.Pattern;
|
||||
*/
|
||||
public class FTConfig {
|
||||
|
||||
private static final Pattern RECFM_PATTERN = Pattern.compile("(?i)RECFM[\\s\\(]+([FVU]|FIXED|VARIABLE|UNDEFINED)\\)?");
|
||||
private static final Pattern RECFM_PATTERN = Pattern.compile("(?i)RECFM[\\s\\(]+([FVU]|FB|VB|VBS|FBS|FIXED|VARIABLE|UNDEFINED)\\)?");
|
||||
private static final Pattern LRECL_PATTERN = Pattern.compile("(?i)LRECL[\\s\\(]+(\\d+)\\)?");
|
||||
private static final Pattern BLK_PATTERN = Pattern.compile("(?i)(?:BLKSIZE|BLOCK)[\\s\\(]+(\\d+)\\)?");
|
||||
private static final Pattern SPACE_PATTERN = Pattern.compile("(?i)SPACE[\\s\\(]+(\\d+)(?:[\\s,]+(\\d+))?\\)?");
|
||||
private static final Pattern SPACE_PATTERN = Pattern.compile("(?i)SPACE[\\s\\(]+(\\d+)(?:[\\s,]+(\\d+))?(?:[\\s,]+(\\d+))?\\)?");
|
||||
private static final Pattern DIR_PATTERN = Pattern.compile("(?i)(?:DIR|DIRECTORY)[\\s\\(]+(\\d+)\\)?");
|
||||
private static final Pattern AVB_PATTERN = Pattern.compile("(?i)AVBLOCK[\\s\\(]+(\\d+)\\)?");
|
||||
private static final Pattern CP_PATTERN = Pattern.compile("(?i)CODEPAGE[\\s\\(]+([A-Za-z0-9_-]+)\\)?");
|
||||
private static final Pattern MTU_PATTERN = Pattern.compile("(?i)(?:BUFFERSIZE|MTU|BUFSIZE)[\\s\\(]+(\\d+)\\)?");
|
||||
@@ -68,6 +69,7 @@ public class FTConfig {
|
||||
private int blksize = 0;
|
||||
private int primarySpace = 0;
|
||||
private int secondarySpace = 0;
|
||||
private int directoryBlocks = 0;
|
||||
private int avblock = 0;
|
||||
private int dftBufferSize = FTConstants.DFT_BUF;
|
||||
private String otherOptions = null;
|
||||
@@ -172,6 +174,20 @@ public class FTConfig {
|
||||
public int getSecondarySpace() { return secondarySpace; }
|
||||
public void setSecondarySpace(int secondarySpace) { this.secondarySpace = secondarySpace; }
|
||||
|
||||
public int getDirectoryBlocks() { return directoryBlocks; }
|
||||
public void setDirectoryBlocks(int directoryBlocks) { this.directoryBlocks = Math.max(0, directoryBlocks); }
|
||||
public void setDirectoryBlocks(String dir) {
|
||||
if (dir == null || dir.trim().isEmpty()) {
|
||||
this.directoryBlocks = 0;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.directoryBlocks = Math.max(0, Integer.parseInt(dir.trim()));
|
||||
} catch (NumberFormatException e) {
|
||||
this.directoryBlocks = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public int getAvblock() { return avblock; }
|
||||
public void setAvblock(int avblock) { this.avblock = avblock; }
|
||||
|
||||
@@ -217,9 +233,9 @@ public class FTConfig {
|
||||
this.recfm = RecordFormat.DEFAULT;
|
||||
} else {
|
||||
String u = r.trim().toUpperCase();
|
||||
if (u.equals("F") || u.startsWith("FIXED")) this.recfm = RecordFormat.FIXED;
|
||||
else if (u.equals("V") || u.startsWith("VAR")) this.recfm = RecordFormat.VARIABLE;
|
||||
else if (u.equals("U") || u.startsWith("UNDEF")) this.recfm = RecordFormat.UNDEFINED;
|
||||
if (u.startsWith("F") || u.startsWith("FIXED")) this.recfm = RecordFormat.FIXED;
|
||||
else if (u.startsWith("V") || u.startsWith("VAR")) this.recfm = RecordFormat.VARIABLE;
|
||||
else if (u.startsWith("U") || u.startsWith("UNDEF")) this.recfm = RecordFormat.UNDEFINED;
|
||||
else this.recfm = RecordFormat.DEFAULT;
|
||||
}
|
||||
}
|
||||
@@ -233,14 +249,23 @@ public class FTConfig {
|
||||
}
|
||||
|
||||
public void setSpace(String s) {
|
||||
// Space string like "10,5" for primary/secondary
|
||||
// Space string like "10,5" or "10,5,10" for primary/secondary/directory
|
||||
if (s == null || s.trim().isEmpty()) return;
|
||||
String[] parts = s.split(",");
|
||||
try {
|
||||
if (parts.length > 0) this.primarySpace = Integer.parseInt(parts[0].trim());
|
||||
if (parts.length > 1) this.secondarySpace = Integer.parseInt(parts[1].trim());
|
||||
} catch (NumberFormatException e) {
|
||||
// ignore
|
||||
if (parts.length > 0 && !parts[0].trim().isEmpty()) {
|
||||
this.primarySpace = Integer.parseInt(parts[0].trim());
|
||||
if (this.units == AllocationUnit.DEFAULT) {
|
||||
this.units = AllocationUnit.TRACKS;
|
||||
}
|
||||
}
|
||||
if (parts.length > 1 && !parts[1].trim().isEmpty()) {
|
||||
this.secondarySpace = Integer.parseInt(parts[1].trim());
|
||||
}
|
||||
if (parts.length > 2 && !parts[2].trim().isEmpty()) {
|
||||
this.directoryBlocks = Math.max(0, Integer.parseInt(parts[2].trim()));
|
||||
}
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,6 +304,22 @@ public class FTConfig {
|
||||
if (spaceMatcher.group(2) != null) {
|
||||
this.secondarySpace = Integer.parseInt(spaceMatcher.group(2));
|
||||
}
|
||||
if (spaceMatcher.group(3) != null) {
|
||||
this.directoryBlocks = Math.max(0, Integer.parseInt(spaceMatcher.group(3)));
|
||||
}
|
||||
if (this.units == AllocationUnit.DEFAULT) {
|
||||
this.units = AllocationUnit.TRACKS;
|
||||
}
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
|
||||
Matcher dirMatcher = DIR_PATTERN.matcher(trimmed);
|
||||
if (dirMatcher.find()) {
|
||||
try {
|
||||
this.directoryBlocks = Math.max(0, Integer.parseInt(dirMatcher.group(1)));
|
||||
if (this.units == AllocationUnit.DEFAULT) {
|
||||
this.units = AllocationUnit.TRACKS;
|
||||
}
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
|
||||
@@ -568,14 +609,18 @@ public class FTConfig {
|
||||
opts.append(" BLKSIZE(").append(blksize).append(")");
|
||||
}
|
||||
}
|
||||
if (units != AllocationUnit.DEFAULT) {
|
||||
if (units != AllocationUnit.DEFAULT || primarySpace > 0 || directoryBlocks > 0) {
|
||||
AllocationUnit effectiveUnits = (units != AllocationUnit.DEFAULT) ? units : AllocationUnit.TRACKS;
|
||||
if (opts.length() > 0) opts.append(" ");
|
||||
opts.append("SPACE(").append(primarySpace);
|
||||
if (secondarySpace > 0) {
|
||||
if (secondarySpace > 0 || directoryBlocks > 0) {
|
||||
opts.append(",").append(secondarySpace);
|
||||
}
|
||||
if (directoryBlocks > 0) {
|
||||
opts.append(",").append(directoryBlocks);
|
||||
}
|
||||
opts.append(")");
|
||||
switch (units) {
|
||||
switch (effectiveUnits) {
|
||||
case TRACKS: opts.append(" TRACKS"); break;
|
||||
case CYLINDERS: opts.append(" CYLINDERS"); break;
|
||||
case AVBLOCK: opts.append(" AVBLOCK(").append(avblock).append(")"); break;
|
||||
|
||||
@@ -17,7 +17,7 @@ public class CMSDirectoryParser {
|
||||
return entries;
|
||||
}
|
||||
|
||||
String[] lines = text.split("\r?\n");
|
||||
String[] lines = splitIntoLines(text);
|
||||
|
||||
for (String line : lines) {
|
||||
String trimmed = line.trim();
|
||||
@@ -26,6 +26,7 @@ public class CMSDirectoryParser {
|
||||
|
||||
String upper = trimmed.toUpperCase();
|
||||
if (upper.startsWith("FILENAME") || upper.startsWith("DIRECTORY") || upper.startsWith("FILELIST")) continue;
|
||||
if (upper.startsWith("READY;") || upper.equals("READY") || upper.startsWith("MORE...") || upper.startsWith("HOLDING")) continue;
|
||||
|
||||
CMSDirectoryEntry entry = parseCmsLine(trimmed);
|
||||
if (entry != null) {
|
||||
@@ -36,6 +37,24 @@ public class CMSDirectoryParser {
|
||||
return entries;
|
||||
}
|
||||
|
||||
private static String[] splitIntoLines(String text) {
|
||||
if (text.contains("\n") || text.contains("\r")) {
|
||||
return text.split("\r?\n");
|
||||
}
|
||||
if (text.length() >= 80) {
|
||||
int lineLen = (text.length() % 132 == 0 && text.length() % 80 != 0) ? 132 : 80;
|
||||
int numLines = (text.length() + lineLen - 1) / lineLen;
|
||||
String[] lines = new String[numLines];
|
||||
for (int i = 0; i < numLines; i++) {
|
||||
int start = i * lineLen;
|
||||
int end = Math.min(start + lineLen, text.length());
|
||||
lines[i] = text.substring(start, end);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
return new String[]{text};
|
||||
}
|
||||
|
||||
private static CMSDirectoryEntry parseCmsLine(String line) {
|
||||
String[] tokens = line.split("\\s+");
|
||||
if (tokens.length < 2) return null;
|
||||
|
||||
@@ -27,7 +27,7 @@ public class TSODirectoryParser {
|
||||
return entries;
|
||||
}
|
||||
|
||||
String[] lines = text.split("\r?\n");
|
||||
String[] lines = splitIntoLines(text);
|
||||
|
||||
// Check if text is LISTCAT format
|
||||
if (text.toUpperCase().contains("NONVSAM") || text.toUpperCase().contains("IN-CAT")) {
|
||||
@@ -39,7 +39,8 @@ public class TSODirectoryParser {
|
||||
for (String line : lines) {
|
||||
String trimmed = line.trim();
|
||||
if (trimmed.isEmpty()) continue;
|
||||
if (trimmed.startsWith("--") || trimmed.startsWith("==") || trimmed.startsWith("**")) continue;
|
||||
if (trimmed.startsWith("--") || trimmed.startsWith("==") || trimmed.startsWith("**") || trimmed.startsWith("***")) continue;
|
||||
if (trimmed.equalsIgnoreCase("READY") || trimmed.toUpperCase().startsWith("READY;")) continue;
|
||||
if (trimmed.toUpperCase().startsWith("COMMAND") || trimmed.toUpperCase().startsWith("DSLIST") || trimmed.toUpperCase().startsWith("DATA SETS")) continue;
|
||||
|
||||
TSODirectoryEntry entry = parseTabularLine(trimmed);
|
||||
@@ -51,6 +52,24 @@ public class TSODirectoryParser {
|
||||
return entries;
|
||||
}
|
||||
|
||||
private static String[] splitIntoLines(String text) {
|
||||
if (text.contains("\n") || text.contains("\r")) {
|
||||
return text.split("\r?\n");
|
||||
}
|
||||
if (text.length() >= 80) {
|
||||
int lineLen = (text.length() % 132 == 0 && text.length() % 80 != 0) ? 132 : 80;
|
||||
int numLines = (text.length() + lineLen - 1) / lineLen;
|
||||
String[] lines = new String[numLines];
|
||||
for (int i = 0; i < numLines; i++) {
|
||||
int start = i * lineLen;
|
||||
int end = Math.min(start + lineLen, text.length());
|
||||
lines[i] = text.substring(start, end);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
return new String[]{text};
|
||||
}
|
||||
|
||||
private static void parseListcat(String[] lines, List<TSODirectoryEntry> entries) {
|
||||
TSODirectoryEntry current = null;
|
||||
for (String line : lines) {
|
||||
|
||||
@@ -37,6 +37,7 @@ public class FileTransferFileObject implements Serializable {
|
||||
private int blksize = 0;
|
||||
private int primarySpace = 0;
|
||||
private int secondarySpace = 0;
|
||||
private int directoryBlocks = 0;
|
||||
private int avblock = 0;
|
||||
private String options = "";
|
||||
|
||||
@@ -201,6 +202,14 @@ public class FileTransferFileObject implements Serializable {
|
||||
this.secondarySpace = secondarySpace;
|
||||
}
|
||||
|
||||
public int getDirectoryBlocks() {
|
||||
return directoryBlocks;
|
||||
}
|
||||
|
||||
public void setDirectoryBlocks(int directoryBlocks) {
|
||||
this.directoryBlocks = Math.max(0, directoryBlocks);
|
||||
}
|
||||
|
||||
public int getAvblock() {
|
||||
return avblock;
|
||||
}
|
||||
@@ -241,6 +250,7 @@ public class FileTransferFileObject implements Serializable {
|
||||
config.setBlksize(blksize);
|
||||
config.setPrimarySpace(primarySpace);
|
||||
config.setSecondarySpace(secondarySpace);
|
||||
config.setDirectoryBlocks(directoryBlocks);
|
||||
config.setAvblock(avblock);
|
||||
if (options != null && !options.isEmpty()) {
|
||||
config.parseOptions(options);
|
||||
@@ -263,6 +273,7 @@ public class FileTransferFileObject implements Serializable {
|
||||
this.blksize = config.getBlksize();
|
||||
this.primarySpace = config.getPrimarySpace();
|
||||
this.secondarySpace = config.getSecondarySpace();
|
||||
this.directoryBlocks = config.getDirectoryBlocks();
|
||||
this.avblock = config.getAvblock();
|
||||
}
|
||||
|
||||
|
||||
@@ -603,10 +603,73 @@ public class Xfer3270 implements FileTransferInterface {
|
||||
if (callback != null) callback.setStatus(FileTransferHostDirectoryInterface.STATUS_PROCESSING);
|
||||
Vector result = new Vector();
|
||||
|
||||
String dataToParse = hostQuery;
|
||||
if (hostQuery == null || (!hostQuery.contains("\n") && !hostQuery.contains("\r")
|
||||
&& !hostQuery.toUpperCase().contains("NONVSAM") && !hostQuery.toUpperCase().contains("IN-CAT")
|
||||
&& !hostQuery.toUpperCase().contains("FORMAT"))) {
|
||||
if (session != null && session.getPS() != null) {
|
||||
try {
|
||||
ECLPS ps = session.getPS();
|
||||
ps.SendKeys("[clear]");
|
||||
if (session.getOIA() != null) {
|
||||
session.getOIA().waitForInput(3000);
|
||||
}
|
||||
if (hostType == VM_CMS) {
|
||||
String filter = (hostQuery != null && !hostQuery.trim().isEmpty()) ? hostQuery.trim() : "* * *";
|
||||
ps.SendKeys("LISTFILE " + filter + " ( nohead alloc date[enter]");
|
||||
} else {
|
||||
String filter = (hostQuery != null && !hostQuery.trim().isEmpty()) ? hostQuery.trim() : "";
|
||||
if (filter.isEmpty()) {
|
||||
ps.SendKeys("LISTCAT[enter]");
|
||||
} else {
|
||||
if (!filter.startsWith("'")) filter = "'" + filter + "'";
|
||||
ps.SendKeys("LISTCAT LEVEL(" + filter + ")[enter]");
|
||||
}
|
||||
}
|
||||
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
|
||||
if (session.getOIA() != null) {
|
||||
session.getOIA().waitForInput(5000);
|
||||
}
|
||||
StringBuilder sbAll = new StringBuilder();
|
||||
for (int page = 0; page < 20; page++) {
|
||||
int rows = ps.getRows();
|
||||
int cols = ps.getCols();
|
||||
String screen = ps.copyString(0, 0, rows - 1, cols - 1);
|
||||
sbAll.append(screen).append("\n");
|
||||
|
||||
boolean hasMore = false;
|
||||
if (hostType == VM_CMS) {
|
||||
if (screen.contains("MORE...") || screen.contains("HOLDING")) {
|
||||
hasMore = true;
|
||||
ps.SendKeys("[enter]");
|
||||
}
|
||||
} else {
|
||||
if (screen.contains("***")) {
|
||||
hasMore = true;
|
||||
ps.SendKeys("[enter]");
|
||||
}
|
||||
}
|
||||
|
||||
if (hasMore) {
|
||||
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
|
||||
if (session.getOIA() != null) {
|
||||
session.getOIA().waitForInput(3000);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
dataToParse = sbAll.toString();
|
||||
} catch (Exception ignored) {
|
||||
// Fall back to hostQuery
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (hostType == VM_CMS) {
|
||||
if (callback != null) callback.setStatus(FileTransferHostDirectoryInterface.STATUS_RECEIVING);
|
||||
List<CMSDirectoryEntry> cmsEntries = CMSDirectoryParser.parse(hostQuery);
|
||||
List<CMSDirectoryEntry> cmsEntries = CMSDirectoryParser.parse(dataToParse);
|
||||
for (CMSDirectoryEntry entry : cmsEntries) {
|
||||
String hName = entry.getFilename() + " " + entry.getFiletype();
|
||||
String lName = entry.getFilename() + "." + entry.getFiletype();
|
||||
@@ -622,7 +685,7 @@ public class Xfer3270 implements FileTransferInterface {
|
||||
}
|
||||
} else {
|
||||
if (callback != null) callback.setStatus(FileTransferHostDirectoryInterface.STATUS_RECEIVING);
|
||||
List<TSODirectoryEntry> tsoEntries = TSODirectoryParser.parse(hostQuery);
|
||||
List<TSODirectoryEntry> tsoEntries = TSODirectoryParser.parse(dataToParse);
|
||||
for (TSODirectoryEntry entry : tsoEntries) {
|
||||
String name = entry.getName();
|
||||
if (hostFiles != null) hostFiles.addElement(name);
|
||||
|
||||
@@ -125,4 +125,66 @@ public class FTConfigTest {
|
||||
String cmd = config.buildCommand();
|
||||
assertEquals("IND$FILE PUT FILE01 (BINARY NOCRLF REPLACE", cmd);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPdsAllocationCommand() {
|
||||
FTConfig config = new FTConfig();
|
||||
config.setHostType(FTConfig.HostType.TSO);
|
||||
config.setDirection(FTConfig.Direction.SEND);
|
||||
config.setHostFilename("'USER.PDS.DATA'");
|
||||
config.setTransferMode(FTConfig.TransferMode.ASCII);
|
||||
config.setCrAction(FTConfig.CrAction.REMOVE);
|
||||
config.setRecfm("FB");
|
||||
config.setLrecl("80");
|
||||
config.setBlksize("3120");
|
||||
config.setPrimarySpace(15);
|
||||
config.setSecondarySpace(5);
|
||||
config.setDirectoryBlocks(10);
|
||||
config.setUnits(FTConfig.AllocationUnit.TRACKS);
|
||||
|
||||
String cmd = config.buildCommand();
|
||||
assertEquals("IND$FILE PUT 'USER.PDS.DATA' ASCII CRLF RECFM(F) LRECL(80) BLKSIZE(3120) SPACE(15,5,10) TRACKS", cmd);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPdsParseOptionsThreeParameters() {
|
||||
FTConfig config = new FTConfig();
|
||||
config.setDirection(FTConfig.Direction.SEND);
|
||||
config.setHostFilename("SYS1.PARMLIB");
|
||||
config.parseOptions("TSO ASCII CRLF SPACE(20,10,15) TRACKS");
|
||||
|
||||
assertEquals(20, config.getPrimarySpace());
|
||||
assertEquals(10, config.getSecondarySpace());
|
||||
assertEquals(15, config.getDirectoryBlocks());
|
||||
assertEquals(FTConfig.AllocationUnit.TRACKS, config.getUnits());
|
||||
assertEquals("IND$FILE PUT SYS1.PARMLIB ASCII CRLF SPACE(20,10,15) TRACKS", config.buildCommand());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPdsParseOptionsWithDirToken() {
|
||||
FTConfig config = new FTConfig();
|
||||
config.setDirection(FTConfig.Direction.SEND);
|
||||
config.setHostFilename("SYS1.PROCLIB");
|
||||
config.parseOptions("TSO ASCII CRLF SPACE(10,5) DIR(20) CYLINDERS");
|
||||
|
||||
assertEquals(10, config.getPrimarySpace());
|
||||
assertEquals(5, config.getSecondarySpace());
|
||||
assertEquals(20, config.getDirectoryBlocks());
|
||||
assertEquals(FTConfig.AllocationUnit.CYLINDERS, config.getUnits());
|
||||
assertEquals("IND$FILE PUT SYS1.PROCLIB ASCII CRLF SPACE(10,5,20) CYLINDERS", config.buildCommand());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPdsSetSpaceWithThreeArguments() {
|
||||
FTConfig config = new FTConfig();
|
||||
config.setDirection(FTConfig.Direction.SEND);
|
||||
config.setHostFilename("MY.PDS");
|
||||
config.setSpace("12,4,8");
|
||||
|
||||
assertEquals(12, config.getPrimarySpace());
|
||||
assertEquals(4, config.getSecondarySpace());
|
||||
assertEquals(8, config.getDirectoryBlocks());
|
||||
assertEquals(FTConfig.AllocationUnit.TRACKS, config.getUnits());
|
||||
assertTrue(config.buildCommand().contains("SPACE(12,4,8) TRACKS"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,4 +121,34 @@ public class DirectoryParserTest {
|
||||
assertEquals("GENPASS", entries.get(1).getFilename());
|
||||
assertEquals("REXX", entries.get(1).getFiletype());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContinuousScreenBufferParsingCms() {
|
||||
// Continuous 80-character rows without newlines (as produced by unformatted 3270 presentation space)
|
||||
String row1 = String.format("%-80s", "PROFILE EXEC A1 V 80 25 1 2026-05-10 14:22:01");
|
||||
String row2 = String.format("%-80s", "TEST DATA A1 F 80 100 2 2026-08-01 09:15:00");
|
||||
String row3 = String.format("%-80s", "Ready; T=0.01/0.01 12:00:00");
|
||||
String continuousScreen = row1 + row2 + row3;
|
||||
|
||||
List<CMSDirectoryEntry> entries = CMSDirectoryParser.parse(continuousScreen);
|
||||
assertEquals(2, entries.size(), "Should parse 2 CMS entries and ignore Ready;");
|
||||
assertEquals("PROFILE", entries.get(0).getFilename());
|
||||
assertEquals("TEST", entries.get(1).getFilename());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContinuousScreenBufferParsingTso() {
|
||||
// Continuous 80-character rows without newlines for ISPF DSLIST
|
||||
String row1 = String.format("%-80s", "Dslist - Data Sets Matching USER.*");
|
||||
String row2 = String.format("%-80s", "Command - Enter \"/\" to select action Tracks Dsorg Recfm Volume");
|
||||
String row3 = String.format("%-80s", " USER.TEST.CNTL 15 PO FB TSO001");
|
||||
String row4 = String.format("%-80s", " USER.TEST.DATA 10 PS FB TSO001");
|
||||
String row5 = String.format("%-80s", "***");
|
||||
String continuousScreen = row1 + row2 + row3 + row4 + row5;
|
||||
|
||||
List<TSODirectoryEntry> entries = TSODirectoryParser.parse(continuousScreen);
|
||||
assertEquals(2, entries.size(), "Should parse 2 TSO entries and ignore ***");
|
||||
assertEquals("USER.TEST.CNTL", entries.get(0).getName());
|
||||
assertEquals("USER.TEST.DATA", entries.get(1).getName());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user