Merge branch 'main' of ssh://git.zander.im/Zander671/curator

This commit is contained in:
Alexander Rosenberg 2024-10-28 15:36:17 -07:00
commit 797615877b
Signed by: Zander671
GPG Key ID: 5FD0394ADBD72730
13 changed files with 288 additions and 128 deletions

View File

@ -12,9 +12,6 @@ public class Start {
public static final String CURATOR_VERSION = "1.0"; public static final String CURATOR_VERSION = "1.0";
public static void main(String[] args) { public static void main(String[] args) {
// TODO debug
System.setProperty("sun.java2d.uiScale", "2");
registerSecretsFactories(); registerSecretsFactories();
SwingUtilities.invokeLater(() -> new LibrarySelectFrame(args)); SwingUtilities.invokeLater(() -> new LibrarySelectFrame(args));
} }

View File

@ -117,8 +117,9 @@ public class Library {
private LocalCache localCache; private LocalCache localCache;
private RemoteStore remoteStore; private RemoteStore remoteStore;
public Library(File local, URI remote, Secrets secrets) throws ManifestParseException, IOException { public Library(File local, URI remote, boolean passiveMode, Secrets secrets) throws ManifestParseException, IOException {
remoteStore = new RemoteStore(remote, secrets); remoteStore = new RemoteStore(remote, secrets);
remoteStore.setPassiveMode(passiveMode);
localCache = new LocalCache(local); localCache = new LocalCache(local);
} }

View File

@ -7,6 +7,7 @@ import java.net.ProtocolException;
import java.net.URI; import java.net.URI;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.net.PrintCommandListener;
import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile; import org.apache.commons.net.ftp.FTPFile;
@ -34,6 +35,7 @@ public class RemoteStore {
private final FTPClient ftp; private final FTPClient ftp;
private int connectionLevel = 0; private int connectionLevel = 0;
private boolean ignoreManifest = false; private boolean ignoreManifest = false;
private boolean passiveMode;
public RemoteStoreFileTypeHandler fileTypeHandler = new RemoteStoreFileTypeHandler(); public RemoteStoreFileTypeHandler fileTypeHandler = new RemoteStoreFileTypeHandler();
@ -42,10 +44,14 @@ public class RemoteStore {
if (!url.isAbsolute() || url.getScheme().equals("ftp")) { if (!url.isAbsolute() || url.getScheme().equals("ftp")) {
ftp = new FTPClient(); ftp = new FTPClient();
} else if (url.getScheme().equals("ftps")) { } else if (url.getScheme().equals("ftps")) {
ftp = new FTPSClient(false); ftp = new FTPSClient("TLS", false);
((FTPSClient) ftp).setEndpointCheckingEnabled(true);
} else { } else {
throw new ProtocolException("unknown protocol: '" + url.getScheme() + "'"); throw new ProtocolException("unknown protocol: '" + url.getScheme() + "'");
} }
// Uncomment this to print all FTP commands
// ftp.addProtocolCommandListener(new PrintCommandListener(System.out, true));
ftp.setListHiddenFiles(true);
this.secrets = secrets; this.secrets = secrets;
} }
@ -61,6 +67,24 @@ public class RemoteStore {
this.ignoreManifest = ignoreManifest; this.ignoreManifest = ignoreManifest;
} }
public boolean isPassiveMode() {
return passiveMode;
}
public void setPassiveMode(boolean passiveMode) throws IOException {
this.passiveMode = passiveMode;
if (connectionLevel != 0) {
String modeString = passiveMode ? "passive" : "active";
LOGGER.info("Switching to " + modeString + " mode");
if (passiveMode) {
ftp.enterLocalPassiveMode();
} else {
ftp.enterLocalActiveMode();
}
checkFtpResponse("Failed to switch to " + modeString + " mode");
}
}
public void fetchManifest() throws IOException { public void fetchManifest() throws IOException {
doFtpActions(() -> { doFtpActions(() -> {
FTPFile info = fileInformation(MANIFEST_PATH); FTPFile info = fileInformation(MANIFEST_PATH);
@ -199,10 +223,27 @@ public class RemoteStore {
return fileInformation(path) != null; return fileInformation(path) != null;
} }
private boolean isRootDir(String path) {
return path.matches("^(/+\\.{0,2})+$");
}
public FTPFile fileInformation(String path) throws IOException { public FTPFile fileInformation(String path) throws IOException {
if (isRootDir(path)) {
return null;
}
AtomicReference<FTPFile> file = new AtomicReference<FTPFile>(); AtomicReference<FTPFile> file = new AtomicReference<FTPFile>();
doFtpActions(() -> { doFtpActions(() -> {
file.set(ftp.mlistFile(resolvePath(path))); String pp = getParentPath(path);
if (pp == null) {
pp = getBasePath();
}
String name = getFileName(path);
FTPFile[] files = ftp.listFiles(pp);
for (FTPFile f : files) {
if (f.getName().equals(name)) {
file.set(f);
}
}
}); });
return file.get(); return file.get();
} }
@ -216,6 +257,14 @@ public class RemoteStore {
throw new IOException("Invalid ftp secrets! Username: '" + secrets.getUsername() + "'"); throw new IOException("Invalid ftp secrets! Username: '" + secrets.getUsername() + "'");
} }
LOGGER.info("Logged info ftp as user '{}'", secrets.getUsername()); LOGGER.info("Logged info ftp as user '{}'", secrets.getUsername());
if (ftp instanceof FTPSClient) {
FTPSClient ftps = (FTPSClient) ftp;
ftps.execPBSZ(0);
checkFtpResponse("Failed to send PBSZ command");
ftps.execPROT("P");
checkFtpResponse("Failed to send PROT command");
}
setPassiveMode(passiveMode);
} }
} }
@ -307,7 +356,10 @@ public class RemoteStore {
pb.append("/"); pb.append("/");
ftp.makeDirectory(pb.toString()); ftp.makeDirectory(pb.toString());
} }
checkFtpResponse("Could not create directory: " + pp); info = fileInformation(pp);
// if (info == null || !info.isDirectory()) {
// throw new IOException("Failed to create directory: " + pp);
// }
LOGGER.info("Created directory: {}", pp); LOGGER.info("Created directory: {}", pp);
} else if (!info.isDirectory()) { } else if (!info.isDirectory()) {
throw new IOException("Not a directory: " + pp); throw new IOException("Not a directory: " + pp);
@ -319,8 +371,15 @@ public class RemoteStore {
if (ftp.isConnected()) { if (ftp.isConnected()) {
int r = ftp.getReplyCode(); int r = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(r)) { if (!FTPReply.isPositiveCompletion(r)) {
LOGGER.info(error); String replyString = ftp.getReplyString();
throw new IOException(error); if (replyString.endsWith("\n")) {
replyString = replyString.substring(0, replyString.length() - 1);
}
if (replyString.endsWith(".")) {
replyString = replyString.substring(0, replyString.length() - 1);
}
LOGGER.info("{}: {}", error, replyString);
throw new IOException(error + ": " + replyString);
} }
} }
} }
@ -365,6 +424,19 @@ public class RemoteStore {
if (i == -1) { if (i == -1) {
return null; return null;
} }
return cp.substring(0, i); String pp = cp.substring(0, i);
while (!pp.isEmpty() && pp.charAt(pp.length() - 1) == '/') {
pp = pp.substring(0, pp.length() - 1);
}
return pp;
}
private static String getFileName(String path) {
String cp = cleanPath(path);
int i = cp.lastIndexOf("/");
if (i == -1) {
return cp;
}
return cp.substring(i + 1);
} }
} }

View File

@ -33,7 +33,7 @@ public class PassSecretsFactory extends SecretsFactory {
public static boolean isBackendSupported() { public static boolean isBackendSupported() {
try { try {
Process p = Runtime.getRuntime().exec("which pass"); Process p = Runtime.getRuntime().exec(new String[] {"which", "pass"});
if (!p.waitFor(1000, TimeUnit.MILLISECONDS)) { if (!p.waitFor(1000, TimeUnit.MILLISECONDS)) {
return false; return false;
} }

View File

@ -12,6 +12,7 @@ import javax.swing.Box;
import javax.swing.BoxLayout; import javax.swing.BoxLayout;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JCheckBox; import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog; import javax.swing.JDialog;
import javax.swing.JFileChooser; import javax.swing.JFileChooser;
import javax.swing.JLabel; import javax.swing.JLabel;
@ -44,6 +45,8 @@ public class LibraryCreateDialog extends JDialog {
private final JTextField usernameField; private final JTextField usernameField;
private final JLabel passwordLabel; private final JLabel passwordLabel;
private final JPasswordField passwordField; private final JPasswordField passwordField;
private final JLabel ftpModeLabel;
private final JComboBox<String> ftpModeCombo;
private final JCheckBox defaultPathCheck; private final JCheckBox defaultPathCheck;
private final JLabel pathLabel; private final JLabel pathLabel;
private final FileChooserField pathField; private final FileChooserField pathField;
@ -66,6 +69,8 @@ public class LibraryCreateDialog extends JDialog {
usernameField = new JTextField(10); usernameField = new JTextField(10);
passwordLabel = new JLabel("Password:"); passwordLabel = new JLabel("Password:");
passwordField = new JPasswordField(10); passwordField = new JPasswordField(10);
ftpModeLabel = new JLabel("FTP Mode:");
ftpModeCombo = new JComboBox<String>(new String[] { "Passive", "Active" });
defaultPathCheck = new JCheckBox("Use default library location"); defaultPathCheck = new JCheckBox("Use default library location");
defaultPathCheck.setSelected(true); defaultPathCheck.setSelected(true);
pathLabel = new JLabel("Library Path:"); pathLabel = new JLabel("Library Path:");
@ -223,12 +228,21 @@ public class LibraryCreateDialog extends JDialog {
entryPanel.add(passwordField, gbc); entryPanel.add(passwordField, gbc);
gbc.gridy = 5; gbc.gridy = 5;
gbc.gridx = 0; gbc.gridx = 0;
gbc.fill = GridBagConstraints.NONE;
gbc.anchor = GridBagConstraints.LINE_END;
entryPanel.add(ftpModeLabel, gbc);
gbc.gridx = 1;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.anchor = GridBagConstraints.LINE_START;
entryPanel.add(ftpModeCombo, gbc);
gbc.gridy = 6;
gbc.gridx = 0;
gbc.anchor = GridBagConstraints.CENTER; gbc.anchor = GridBagConstraints.CENTER;
gbc.fill = GridBagConstraints.NONE; gbc.fill = GridBagConstraints.NONE;
gbc.gridwidth = 2; gbc.gridwidth = 2;
entryPanel.add(defaultPathCheck, gbc); entryPanel.add(defaultPathCheck, gbc);
gbc.gridwidth = 1; gbc.gridwidth = 1;
gbc.gridy = 6; gbc.gridy = 7;
gbc.gridx = 0; gbc.gridx = 0;
gbc.anchor = GridBagConstraints.LINE_END; gbc.anchor = GridBagConstraints.LINE_END;
entryPanel.add(pathLabel, gbc); entryPanel.add(pathLabel, gbc);
@ -266,4 +280,8 @@ public class LibraryCreateDialog extends JDialog {
return pathField.getPathField().getText(); return pathField.getPathField().getText();
} }
public boolean isFtpPassiveMode() {
return ftpModeCombo.getSelectedItem().equals("Passive");
}
} }

View File

@ -12,6 +12,7 @@ import javax.swing.Box;
import javax.swing.BoxLayout; import javax.swing.BoxLayout;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JCheckBox; import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog; import javax.swing.JDialog;
import javax.swing.JFileChooser; import javax.swing.JFileChooser;
import javax.swing.JLabel; import javax.swing.JLabel;
@ -44,6 +45,8 @@ public class LibraryEditDialog extends JDialog {
private final JTextField usernameField; private final JTextField usernameField;
private final JLabel passwordLabel; private final JLabel passwordLabel;
private final JPasswordField passwordField; private final JPasswordField passwordField;
private final JLabel ftpModeLabel;
private final JComboBox<String> ftpModeCombo;
private final JCheckBox defaultPathCheck; private final JCheckBox defaultPathCheck;
private final JLabel pathLabel; private final JLabel pathLabel;
private final FileChooserField pathField; private final FileChooserField pathField;
@ -55,7 +58,9 @@ public class LibraryEditDialog extends JDialog {
private final JPanel buttonPanel; private final JPanel buttonPanel;
private final JPanel panel; private final JPanel panel;
public LibraryEditDialog(Window parent, String name, String url, String ftp, String username, String password, String file, Runnable cacheClearCallback) { public LibraryEditDialog(Window parent, String name, String url, String ftp,
String username, String password, boolean passiveMode, String file,
Runnable cacheClearCallback) {
super(parent, "Edit Library"); super(parent, "Edit Library");
nameLabel = new JLabel("Name:"); nameLabel = new JLabel("Name:");
nameField = new JTextField(10); nameField = new JTextField(10);
@ -72,6 +77,9 @@ public class LibraryEditDialog extends JDialog {
passwordLabel = new JLabel("Password:"); passwordLabel = new JLabel("Password:");
passwordField = new JPasswordField(10); passwordField = new JPasswordField(10);
passwordField.setText(password); passwordField.setText(password);
ftpModeLabel = new JLabel("FTP Mode:");
ftpModeCombo = new JComboBox<String>(new String[] { "Passive", "Active" });
ftpModeCombo.setSelectedIndex(passiveMode ? 0 : 1);
defaultPathCheck = new JCheckBox("Use default library location"); defaultPathCheck = new JCheckBox("Use default library location");
pathLabel = new JLabel("Library Path:"); pathLabel = new JLabel("Library Path:");
pathField = new FileChooserField(10, file, new File(LibrarySelectFrame.DEFAULT_LIBRARY_PATH)); pathField = new FileChooserField(10, file, new File(LibrarySelectFrame.DEFAULT_LIBRARY_PATH));
@ -84,64 +92,64 @@ public class LibraryEditDialog extends JDialog {
pathField.getChooser().setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); pathField.getChooser().setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
pathField.getChooser().setDialogTitle("Select Library Location"); pathField.getChooser().setDialogTitle("Select Library Location");
nameField.getDocument().addDocumentListener(new DocumentListener(){ nameField.getDocument().addDocumentListener(new DocumentListener(){
@Override @Override
public void insertUpdate(DocumentEvent e) { public void insertUpdate(DocumentEvent e) {
update(); update();
} }
@Override @Override
public void removeUpdate(DocumentEvent e) { public void removeUpdate(DocumentEvent e) {
update(); update();
} }
@Override @Override
public void changedUpdate(DocumentEvent e) { public void changedUpdate(DocumentEvent e) {
update(); update();
} }
private void update() { private void update() {
if (defaultPathCheck.isSelected()) {
pathField.getPathField().setText(LibrarySelectFrame.DEFAULT_LIBRARY_PATH + "/" + nameField.getText());
}
}
});
defaultPathCheck.addItemListener((e) -> {
if (defaultPathCheck.isSelected()) { if (defaultPathCheck.isSelected()) {
pathField.getPathField().setText(LibrarySelectFrame.DEFAULT_LIBRARY_PATH + "/" + nameField.getText()); pathField.getPathField().setText(LibrarySelectFrame.DEFAULT_LIBRARY_PATH + "/" + nameField.getText());
pathField.setEnabled(false);
} else {
pathField.setEnabled(true);
} }
}); }
});
defaultPathCheck.addItemListener((e) -> {
if (defaultPathCheck.isSelected()) {
pathField.getPathField().setText(LibrarySelectFrame.DEFAULT_LIBRARY_PATH + "/" + nameField.getText());
pathField.setEnabled(false);
} else {
pathField.setEnabled(true);
}
});
entryPanel = new JPanel(new GridBagLayout()); entryPanel = new JPanel(new GridBagLayout());
buildEntryPanel(); buildEntryPanel();
cancelButton = new JButton("Cancel"); cancelButton = new JButton("Cancel");
cancelButton.setMnemonic(KeyEvent.VK_C); cancelButton.setMnemonic(KeyEvent.VK_C);
cancelButton.addActionListener((e) -> { cancelButton.addActionListener((e) -> {
dispose(); dispose();
}); });
clearCacheButton = new JButton("Clear Cache"); clearCacheButton = new JButton("Clear Cache");
clearCacheButton.setMnemonic(KeyEvent.VK_L); clearCacheButton.setMnemonic(KeyEvent.VK_L);
clearCacheButton.addActionListener((e) -> { clearCacheButton.addActionListener((e) -> {
int c = JOptionPane.showConfirmDialog(this, "Are you sure you would like to clear this library's cache?", int c = JOptionPane.showConfirmDialog(this, "Are you sure you would like to clear this library's cache?",
"Clear Cahce", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); "Clear Cahce", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
if (cacheClearCallback != null && c == JOptionPane.YES_OPTION) { if (cacheClearCallback != null && c == JOptionPane.YES_OPTION) {
cacheClearCallback.run(); cacheClearCallback.run();
} }
}); });
helpButton = new JButton("?"); helpButton = new JButton("?");
helpButton.addActionListener((e) -> DocumentationViewer.show(this, "Libraries/Local Library Settings")); helpButton.addActionListener((e) -> DocumentationViewer.show(this, "Libraries/Local Library Settings"));
saveButton = new JButton("Save"); saveButton = new JButton("Save");
saveButton.setMnemonic(KeyEvent.VK_S); saveButton.setMnemonic(KeyEvent.VK_S);
saveButton.setEnabled(false); saveButton.setEnabled(false);
saveButton.addActionListener((e) -> { saveButton.addActionListener((e) -> {
if (nameField.getText().contains("/")) { if (nameField.getText().contains("/")) {
JOptionPane.showMessageDialog(this, "Library names cannot contain the '/' character!", "Library Error", JOptionPane.ERROR_MESSAGE); JOptionPane.showMessageDialog(this, "Library names cannot contain the '/' character!", "Library Error", JOptionPane.ERROR_MESSAGE);
} else { } else {
dispose(); dispose();
response = RESPONSE_SAVE; response = RESPONSE_SAVE;
} }
}); });
buttonPanel = new JPanel(); buttonPanel = new JPanel();
buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
buttonPanel.add(cancelButton); buttonPanel.add(cancelButton);
@ -150,30 +158,30 @@ public class LibraryEditDialog extends JDialog {
buttonPanel.add(clearCacheButton); buttonPanel.add(clearCacheButton);
buttonPanel.add(saveButton); buttonPanel.add(saveButton);
final DocumentListener formFillListener = new DocumentListener(){ final DocumentListener formFillListener = new DocumentListener(){
@Override @Override
public void insertUpdate(DocumentEvent e) { public void insertUpdate(DocumentEvent e) {
update(); update();
} }
@Override @Override
public void removeUpdate(DocumentEvent e) { public void removeUpdate(DocumentEvent e) {
update(); update();
} }
@Override @Override
public void changedUpdate(DocumentEvent e) { public void changedUpdate(DocumentEvent e) {
update(); update();
} }
private void update() { private void update() {
if (!nameField.getText().isBlank() && !ftpField.getText().isBlank() if (!nameField.getText().isBlank() && !ftpField.getText().isBlank()
&& !usernameField.getText().isBlank() && !pathField.getPathField().getText().isBlank()) { && !usernameField.getText().isBlank() && !pathField.getPathField().getText().isBlank()) {
saveButton.setEnabled(true); saveButton.setEnabled(true);
} else { } else {
saveButton.setEnabled(false); saveButton.setEnabled(false);
}
} }
}; }
};
nameField.getDocument().addDocumentListener(formFillListener); nameField.getDocument().addDocumentListener(formFillListener);
ftpField.getDocument().addDocumentListener(formFillListener); ftpField.getDocument().addDocumentListener(formFillListener);
usernameField.getDocument().addDocumentListener(formFillListener); usernameField.getDocument().addDocumentListener(formFillListener);
@ -245,11 +253,20 @@ public class LibraryEditDialog extends JDialog {
gbc.gridy = 5; gbc.gridy = 5;
gbc.gridx = 0; gbc.gridx = 0;
gbc.fill = GridBagConstraints.NONE; gbc.fill = GridBagConstraints.NONE;
gbc.anchor = GridBagConstraints.LINE_END;
entryPanel.add(ftpModeLabel, gbc);
gbc.gridx = 1;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.anchor = GridBagConstraints.LINE_START;
entryPanel.add(ftpModeCombo, gbc);
gbc.gridy = 6;
gbc.gridx = 0;
gbc.fill = GridBagConstraints.NONE;
gbc.anchor = GridBagConstraints.CENTER; gbc.anchor = GridBagConstraints.CENTER;
gbc.gridwidth = 2; gbc.gridwidth = 2;
entryPanel.add(defaultPathCheck, gbc); entryPanel.add(defaultPathCheck, gbc);
gbc.gridwidth = 1; gbc.gridwidth = 1;
gbc.gridy = 6; gbc.gridy = 7;
gbc.gridx = 0; gbc.gridx = 0;
gbc.anchor = GridBagConstraints.LINE_END; gbc.anchor = GridBagConstraints.LINE_END;
entryPanel.add(pathLabel, gbc); entryPanel.add(pathLabel, gbc);
@ -287,4 +304,8 @@ public class LibraryEditDialog extends JDialog {
return pathField.getPathField().getText(); return pathField.getPathField().getText();
} }
public boolean isFtpPassiveMode() {
return ftpModeCombo.getSelectedItem().equals("Passive");
}
} }

View File

@ -10,6 +10,7 @@ import java.awt.event.KeyEvent;
import javax.swing.Box; import javax.swing.Box;
import javax.swing.BoxLayout; import javax.swing.BoxLayout;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog; import javax.swing.JDialog;
import javax.swing.JFileChooser; import javax.swing.JFileChooser;
import javax.swing.JLabel; import javax.swing.JLabel;
@ -39,6 +40,8 @@ public class LibraryImportDialog extends JDialog {
private final JTextField usernameField; private final JTextField usernameField;
private final JLabel passwordLabel; private final JLabel passwordLabel;
private final JPasswordField passwordField; private final JPasswordField passwordField;
private final JLabel ftpModeLabel;
private final JComboBox<String> ftpModeCombo;
private final JLabel fileLabel; private final JLabel fileLabel;
private final FileChooserField fileField; private final FileChooserField fileField;
private final JPanel entryPanel; private final JPanel entryPanel;
@ -58,6 +61,8 @@ public class LibraryImportDialog extends JDialog {
usernameField = new JTextField(10); usernameField = new JTextField(10);
passwordLabel = new JLabel("Password:"); passwordLabel = new JLabel("Password:");
passwordField = new JPasswordField(10); passwordField = new JPasswordField(10);
ftpModeLabel = new JLabel("FTP Mode:");
ftpModeCombo = new JComboBox<String>(new String[] { "Passive", "Active" });
fileLabel = new JLabel("Source:"); fileLabel = new JLabel("Source:");
fileField = new FileChooserField(10); fileField = new FileChooserField(10);
fileField.getChooser().setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); fileField.getChooser().setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
@ -69,17 +74,17 @@ public class LibraryImportDialog extends JDialog {
cancelButton = new JButton("Cancel"); cancelButton = new JButton("Cancel");
cancelButton.setMnemonic(KeyEvent.VK_C); cancelButton.setMnemonic(KeyEvent.VK_C);
cancelButton.addActionListener((e) -> { cancelButton.addActionListener((e) -> {
dispose(); dispose();
}); });
helpButton = new JButton("?"); helpButton = new JButton("?");
helpButton.addActionListener((e) -> DocumentationViewer.show(this, "Libraries/Local Library Settings")); helpButton.addActionListener((e) -> DocumentationViewer.show(this, "Libraries/Local Library Settings"));
importButton = new JButton("Import"); importButton = new JButton("Import");
importButton.setMnemonic(KeyEvent.VK_I); importButton.setMnemonic(KeyEvent.VK_I);
importButton.setEnabled(false); importButton.setEnabled(false);
importButton.addActionListener((e) -> { importButton.addActionListener((e) -> {
dispose(); dispose();
response = RESPONSE_IMPORT; response = RESPONSE_IMPORT;
}); });
buttonPanel = new JPanel(); buttonPanel = new JPanel();
buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
buttonPanel.add(cancelButton); buttonPanel.add(cancelButton);
@ -87,26 +92,26 @@ public class LibraryImportDialog extends JDialog {
buttonPanel.add(helpButton); buttonPanel.add(helpButton);
buttonPanel.add(importButton); buttonPanel.add(importButton);
final DocumentListener formFillListener = new DocumentListener(){ final DocumentListener formFillListener = new DocumentListener(){
@Override @Override
public void insertUpdate(DocumentEvent e) { public void insertUpdate(DocumentEvent e) {
update(); update();
} }
@Override @Override
public void removeUpdate(DocumentEvent e) { public void removeUpdate(DocumentEvent e) {
update(); update();
} }
@Override @Override
public void changedUpdate(DocumentEvent e) { public void changedUpdate(DocumentEvent e) {
update(); update();
} }
private void update() { private void update() {
importButton.setEnabled(!ftpField.getText().isBlank() && !usernameField.getText().isBlank() && !fileField.getPathField().getText().isBlank()); importButton.setEnabled(!ftpField.getText().isBlank() && !usernameField.getText().isBlank() && !fileField.getPathField().getText().isBlank());
} }
}; };
ftpField.getDocument().addDocumentListener(formFillListener); ftpField.getDocument().addDocumentListener(formFillListener);
usernameField.getDocument().addDocumentListener(formFillListener); usernameField.getDocument().addDocumentListener(formFillListener);
fileField.getPathField().getDocument().addDocumentListener(formFillListener); fileField.getPathField().getDocument().addDocumentListener(formFillListener);
@ -167,6 +172,15 @@ public class LibraryImportDialog extends JDialog {
gbc.gridx = 0; gbc.gridx = 0;
gbc.anchor = GridBagConstraints.LINE_END; gbc.anchor = GridBagConstraints.LINE_END;
gbc.fill = GridBagConstraints.NONE; gbc.fill = GridBagConstraints.NONE;
entryPanel.add(ftpModeLabel, gbc);
gbc.gridx = 1;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.anchor = GridBagConstraints.LINE_START;
entryPanel.add(ftpModeCombo, gbc);
gbc.gridy = 5;
gbc.gridx = 0;
gbc.anchor = GridBagConstraints.LINE_END;
gbc.fill = GridBagConstraints.NONE;
entryPanel.add(fileLabel, gbc); entryPanel.add(fileLabel, gbc);
gbc.gridx = 1; gbc.gridx = 1;
gbc.fill = GridBagConstraints.HORIZONTAL; gbc.fill = GridBagConstraints.HORIZONTAL;
@ -198,4 +212,8 @@ public class LibraryImportDialog extends JDialog {
return fileField.getPathField().getText(); return fileField.getPathField().getText();
} }
public boolean isFtpPassiveMode() {
return ftpModeCombo.getSelectedItem().equals("Passive");
}
} }

View File

@ -80,12 +80,15 @@ public class LibrarySelectFrame extends JFrame {
public String name; public String name;
public String url; public String url;
public String ftp; public String ftp;
public boolean passiveMode;
public File file; public File file;
public LibraryEntry(String name, String url, String ftp, File file) { public LibraryEntry(String name, String url, String ftp,
boolean passiveMode, File file) {
this.name = name; this.name = name;
this.url = url; this.url = url;
this.ftp = ftp; this.ftp = ftp;
this.passiveMode = passiveMode;
this.file = file; this.file = file;
} }
@ -101,7 +104,9 @@ public class LibrarySelectFrame extends JFrame {
public boolean equals(Object o) { public boolean equals(Object o) {
if (o instanceof LibraryEntry) { if (o instanceof LibraryEntry) {
LibraryEntry e = (LibraryEntry) o; LibraryEntry e = (LibraryEntry) o;
return e.name.equals(name) && e.url.equals(url) && e.file.getPath().equals(file.getPath()); return e.name.equals(name) && e.url.equals(url) &&
e.file.getPath().equals(file.getPath()) &&
e.passiveMode == passiveMode;
} }
return false; return false;
} }
@ -201,7 +206,8 @@ public class LibrarySelectFrame extends JFrame {
nld.setVisible(true); nld.setVisible(true);
if (nld.getResponse() == LibraryCreateDialog.RESPONSE_CREATE) { if (nld.getResponse() == LibraryCreateDialog.RESPONSE_CREATE) {
createLibrary(nld.getLibraryName(), nld.getURL(), nld.getFTP(), createLibrary(nld.getLibraryName(), nld.getURL(), nld.getFTP(),
nld.getUsername(), nld.getPassword(), new File(nld.getLibraryFile())); nld.getUsername(), nld.getPassword(),
nld.isFtpPassiveMode(), new File(nld.getLibraryFile()));
} }
}); });
importItem = new JMenuItem("Import"); importItem = new JMenuItem("Import");
@ -209,7 +215,8 @@ public class LibrarySelectFrame extends JFrame {
LibraryImportDialog lid = new LibraryImportDialog(this); LibraryImportDialog lid = new LibraryImportDialog(this);
lid.setVisible(true); lid.setVisible(true);
if (lid.getResponse() == LibraryImportDialog.RESPONSE_IMPORT) { if (lid.getResponse() == LibraryImportDialog.RESPONSE_IMPORT) {
importLibrary(lid.getURL(), lid.getFTP(), lid.getUsername(), lid.getPassword(), new File(lid.getFile())); importLibrary(lid.getURL(), lid.getFTP(), lid.getUsername(),
lid.getPassword(), lid.isFtpPassiveMode(), new File(lid.getFile()));
} }
}); });
listPopup = new JPopupMenu(); listPopup = new JPopupMenu();
@ -287,7 +294,8 @@ public class LibrarySelectFrame extends JFrame {
LibraryImportDialog lid = new LibraryImportDialog(this); LibraryImportDialog lid = new LibraryImportDialog(this);
lid.setVisible(true); lid.setVisible(true);
if (lid.getResponse() == LibraryImportDialog.RESPONSE_IMPORT) { if (lid.getResponse() == LibraryImportDialog.RESPONSE_IMPORT) {
importLibrary(lid.getURL(), lid.getFTP(), lid.getUsername(), lid.getPassword(), new File(lid.getFile())); importLibrary(lid.getURL(), lid.getFTP(), lid.getUsername(),
lid.getPassword(), lid.isFtpPassiveMode(), new File(lid.getFile()));
} }
}); });
newButton = new JButton("New"); newButton = new JButton("New");
@ -297,7 +305,8 @@ public class LibrarySelectFrame extends JFrame {
nld.setVisible(true); nld.setVisible(true);
if (nld.getResponse() == LibraryCreateDialog.RESPONSE_CREATE) { if (nld.getResponse() == LibraryCreateDialog.RESPONSE_CREATE) {
createLibrary(nld.getLibraryName(), nld.getURL(), nld.getFTP(), createLibrary(nld.getLibraryName(), nld.getURL(), nld.getFTP(),
nld.getUsername(), nld.getPassword(), new File(nld.getLibraryFile())); nld.getUsername(), nld.getPassword(),
nld.isFtpPassiveMode(), new File(nld.getLibraryFile()));
} }
}); });
deleteButton = new JButton("Delete"); deleteButton = new JButton("Delete");
@ -468,7 +477,9 @@ public class LibrarySelectFrame extends JFrame {
} }
private String formatLibraryUrl(String url) { private String formatLibraryUrl(String url) {
if (!url.matches("^https?\\:\\/\\/.*")) { if (url.isBlank()) {
return "";
} else if (!url.matches("^https?\\:\\/\\/.*")) {
return "https://" + url; return "https://" + url;
} }
return url; return url;
@ -476,9 +487,10 @@ public class LibrarySelectFrame extends JFrame {
private void editLibrary(LibraryEntry en) { private void editLibrary(LibraryEntry en) {
Secrets s = SECRETS_FACTORY.createSecrets(SECRETS_KEY + en.name); Secrets s = SECRETS_FACTORY.createSecrets(SECRETS_KEY + en.name);
LibraryEditDialog led = new LibraryEditDialog(this, en.name, en.url, en.ftp, LibraryEditDialog led = new LibraryEditDialog(this, en.name, en.url,
s.getUsername(), s.getPassword(), en.file.getPath(), en.ftp, s.getUsername(), s.getPassword(),
() -> clearLibraryCache(en)); en.passiveMode, en.file.getPath(),
() -> clearLibraryCache(en));
led.setVisible(true); led.setVisible(true);
if (led.getResponse() == LibraryEditDialog.RESPONSE_SAVE) { if (led.getResponse() == LibraryEditDialog.RESPONSE_SAVE) {
String testName = ""; String testName = "";
@ -506,6 +518,7 @@ public class LibrarySelectFrame extends JFrame {
en.name = led.getLibraryName(); en.name = led.getLibraryName();
en.url = formatLibraryUrl(led.getURL()); en.url = formatLibraryUrl(led.getURL());
en.ftp = led.getFTP(); en.ftp = led.getFTP();
en.passiveMode = led.isFtpPassiveMode();
en.file = nf; en.file = nf;
saveLibraryList(); saveLibraryList();
list.repaint(); list.repaint();
@ -548,7 +561,7 @@ public class LibrarySelectFrame extends JFrame {
settingsPutObject("last-library", en); settingsPutObject("last-library", en);
Secrets s = SECRETS_FACTORY.createSecrets(SECRETS_KEY + en.name); Secrets s = SECRETS_FACTORY.createSecrets(SECRETS_KEY + en.name);
try { try {
Library lib = new Library(en.file, new URI(en.ftp), s); Library lib = new Library(en.file, new URI(en.ftp), en.passiveMode, s);
LibrarySyncDialog lsd = new LibrarySyncDialog(null, lib); LibrarySyncDialog lsd = new LibrarySyncDialog(null, lib);
if (lsd.sync() == LibrarySyncDialog.STATUS_OK) { if (lsd.sync() == LibrarySyncDialog.STATUS_OK) {
return lib; return lib;
@ -622,7 +635,8 @@ public class LibrarySelectFrame extends JFrame {
} }
} }
private void importLibrary(String url, String ftp, String username, String password, File dir) { private void importLibrary(String url, String ftp, String username,
String password, boolean passiveMode, File dir) {
File cf = null; File cf = null;
try { try {
cf = dir.getCanonicalFile(); cf = dir.getCanonicalFile();
@ -640,7 +654,7 @@ public class LibrarySelectFrame extends JFrame {
Secrets s = SECRETS_FACTORY.createSecrets(SECRETS_KEY + name); Secrets s = SECRETS_FACTORY.createSecrets(SECRETS_KEY + name);
s.setUsername(username); s.setUsername(username);
s.setPassword(password); s.setPassword(password);
LibraryEntry en = new LibraryEntry(name, url, ftp, cf); LibraryEntry en = new LibraryEntry(name, url, ftp, passiveMode, cf);
LIBRARY_ENTRIES.add(0, en); LIBRARY_ENTRIES.add(0, en);
openLibrary(en, false); openLibrary(en, false);
} }
@ -702,7 +716,8 @@ public class LibrarySelectFrame extends JFrame {
return true; return true;
} }
private void createLibrary(String name, String url, String ftp, String username, String password, File file) { private void createLibrary(String name, String url, String ftp,
String username, String password, boolean passiveMode, File file) {
File cf = null; File cf = null;
try { try {
cf = file.getCanonicalFile(); cf = file.getCanonicalFile();
@ -726,7 +741,7 @@ public class LibrarySelectFrame extends JFrame {
recursiveDelete(cf); recursiveDelete(cf);
break; break;
case LibraryExistsDialog.RESPONSE_IMPORT: case LibraryExistsDialog.RESPONSE_IMPORT:
importLibrary(url, ftp, username, password, file); importLibrary(url, ftp, username, password, passiveMode, file);
return; return;
default: default:
return; return;
@ -756,7 +771,7 @@ public class LibrarySelectFrame extends JFrame {
Secrets s = SECRETS_FACTORY.createSecrets(SECRETS_KEY + name); Secrets s = SECRETS_FACTORY.createSecrets(SECRETS_KEY + name);
s.setUsername(username); s.setUsername(username);
s.setPassword(password); s.setPassword(password);
LibraryEntry en = new LibraryEntry(name, url, ftp, cf); LibraryEntry en = new LibraryEntry(name, url, ftp, passiveMode, cf);
LIBRARY_ENTRIES.add(0, en); LIBRARY_ENTRIES.add(0, en);
openLibrary(en, false); openLibrary(en, false);
} }

View File

@ -3,6 +3,8 @@ package zander.ui.media.map;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.net.URLEncoder; import java.net.URLEncoder;
@ -128,9 +130,12 @@ public class OSMLocationEncoder implements LocationEncoder {
private static URL getURLForQuery(String query) { private static URL getURLForQuery(String query) {
try { try {
final String encodedQuery = URLEncoder.encode(query, StandardCharsets.UTF_8); final String encodedQuery = URLEncoder.encode(query,
return new URL("https://nominatim.openstreetmap.org/search.php?format=jsonv2&q=" + encodedQuery); StandardCharsets.UTF_8);
} catch (MalformedURLException e) { return new URI(
"https://nominatim.openstreetmap.org/search.php?format=jsonv2&q=" +
encodedQuery).toURL();
} catch (URISyntaxException | MalformedURLException e) {
LOGGER.error("Could not generate url for query: '{}'", query, e); LOGGER.error("Could not generate url for query: '{}'", query, e);
throw new Error("Could not create URL!", e); throw new Error("Could not create URL!", e);
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -4,18 +4,22 @@
<h1 style="text-align: center">Local Library Settings</h1> <h1 style="text-align: center">Local Library Settings</h1>
<h2>Overview</h2> <h2>Overview</h2>
<p> <p>
These dialogs create, import or alter the settings of libraries. These settings are primarily These dialogs create, import or alter the settings of libraries. These
for use of Curator and will not affect the Art Museum website. settings are primarily for use of Curator and will not affect the Art
Museum website.
</p> </p>
<p> <p>
<b>Local vs Remote Library</b> <b>Local vs Remote Library</b>
<br> <br>
Remore libraries are instances of the Art Museum website. They are connected to by FTP. Remore libraries are instances of the Art Museum website. They are
Any changes made to them will be reflected in their respective Art Museum instance. connected to by FTP. Any changes made to them will be reflected in their
respective Art Museum instance.
<br> <br>
Local libraries, or caches, are instances of libraries located on your computer. When you connect Local libraries, or caches, are instances of libraries located on your
to a remote library, a local library is automatically created and sync with its remote counterpart. computer. When you connect to a remote library, a local library is
Every time you connect to a remote library, its local counterpart is updated in this way. automatically created and sync with its remote counterpart. Every time
you connect to a remote library, its local counterpart is updated in this
way.
</p> </p>
<img width="500" src="library-create-dialog.png"></img> <img width="500" src="library-create-dialog.png"></img>
<p>The library creation and remote import dialog.</p> <p>The library creation and remote import dialog.</p>
@ -29,16 +33,25 @@
<p>The local library import dialog.</p> <p>The local library import dialog.</p>
<h2>Options</h2> <h2>Options</h2>
<ul> <ul>
<li><b>Name</b>: library name in the select dialog. Does not affect website.</li> <li><b>Name</b>: library name in the select dialog. Does not affect
<li><b>URL</b>: url in the select dialog. Can be blank. Does not affect the website.</li> website.</li>
<li><b>FTP</b>: FTP server to connect to. Must be in the format of 'ftp://hostname:port/path/to/library'. <li><b>URL</b>: url in the select dialog. Can be blank. Does not affect
Protocol can also be FTPS by replacing 'ftp://' with 'ftps://'.</li> the website.</li>
<li><b>Username & Password</b>: username and password to use to connect to FTP server.</li> <li><b>FTP</b>: FTP server to connect to. Must be in the format of
<li><b>Library Location (Source)</b>: location to save caches in. If 'User default library location' 'ftp://hostname:port/path/to/library'. Encryption is supported via
is checked, it will be stored in the directory set in the global settings menu. FTPS. To enable it, replace 'ftp://' with 'ftps://'.</li>
<li><b>Username & Password</b>: username and password to use to connect to
FTP server.</li>
<li><b>FTP Mode</b>: Whether to use active (PORT) or passive (PASV) FTP
mode. If you are unsure, it's best to start with passive and change to
active if you are having trouble connecting.</li>
<li><b>Library Location (Source)</b>: location to save caches in. If 'User
default library location' is checked, it will be stored in the directory
set in the global settings menu.
<a href="help:Libraries/Global Settings">See: Global Settings</a></li> <a href="help:Libraries/Global Settings">See: Global Settings</a></li>
<li><b>Clear Cahce</b>: clear library cahce. This does <b>NOT</b> affect the remote library. It only <li><b>Clear Cahce</b>: clear library cahce. This does <b>NOT</b> affect
clears the local cache. Doing this will cause a full re-download of the remote library next time it is the remote library. It only clears the local cache. Doing this will
cause a full re-download of the remote library next time it is
oppened.</li> oppened.</li>
</ul> </ul>
</body> </body>