diff --git a/src/main/java/zander/Start.java b/src/main/java/zander/Start.java index a0be718..507d68e 100644 --- a/src/main/java/zander/Start.java +++ b/src/main/java/zander/Start.java @@ -12,9 +12,6 @@ public class Start { public static final String CURATOR_VERSION = "1.0"; public static void main(String[] args) { - // TODO debug - System.setProperty("sun.java2d.uiScale", "2"); - registerSecretsFactories(); SwingUtilities.invokeLater(() -> new LibrarySelectFrame(args)); } diff --git a/src/main/java/zander/library/Library.java b/src/main/java/zander/library/Library.java index a4ad2af..3fa86a2 100644 --- a/src/main/java/zander/library/Library.java +++ b/src/main/java/zander/library/Library.java @@ -117,8 +117,9 @@ public class Library { private LocalCache localCache; 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.setPassiveMode(passiveMode); localCache = new LocalCache(local); } diff --git a/src/main/java/zander/library/RemoteStore.java b/src/main/java/zander/library/RemoteStore.java index 508ffb1..01bd132 100644 --- a/src/main/java/zander/library/RemoteStore.java +++ b/src/main/java/zander/library/RemoteStore.java @@ -7,6 +7,7 @@ import java.net.ProtocolException; import java.net.URI; 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.FTPClient; import org.apache.commons.net.ftp.FTPFile; @@ -34,6 +35,7 @@ public class RemoteStore { private final FTPClient ftp; private int connectionLevel = 0; private boolean ignoreManifest = false; + private boolean passiveMode; public RemoteStoreFileTypeHandler fileTypeHandler = new RemoteStoreFileTypeHandler(); @@ -42,10 +44,14 @@ public class RemoteStore { if (!url.isAbsolute() || url.getScheme().equals("ftp")) { ftp = new FTPClient(); } else if (url.getScheme().equals("ftps")) { - ftp = new FTPSClient(false); + ftp = new FTPSClient("TLS", false); + ((FTPSClient) ftp).setEndpointCheckingEnabled(true); } else { 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; } @@ -61,6 +67,24 @@ public class RemoteStore { 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 { doFtpActions(() -> { FTPFile info = fileInformation(MANIFEST_PATH); @@ -199,10 +223,27 @@ public class RemoteStore { return fileInformation(path) != null; } + private boolean isRootDir(String path) { + return path.matches("^(/+\\.{0,2})+$"); + } + public FTPFile fileInformation(String path) throws IOException { + if (isRootDir(path)) { + return null; + } AtomicReference file = new AtomicReference(); 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(); } @@ -216,6 +257,14 @@ public class RemoteStore { throw new IOException("Invalid ftp secrets! Username: '" + 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("/"); 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); } else if (!info.isDirectory()) { throw new IOException("Not a directory: " + pp); @@ -319,8 +371,15 @@ public class RemoteStore { if (ftp.isConnected()) { int r = ftp.getReplyCode(); if (!FTPReply.isPositiveCompletion(r)) { - LOGGER.info(error); - throw new IOException(error); + String replyString = ftp.getReplyString(); + 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) { 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); } } diff --git a/src/main/java/zander/library/secrets/PassSecretsFactory.java b/src/main/java/zander/library/secrets/PassSecretsFactory.java index 39180c2..4e1e380 100644 --- a/src/main/java/zander/library/secrets/PassSecretsFactory.java +++ b/src/main/java/zander/library/secrets/PassSecretsFactory.java @@ -33,7 +33,7 @@ public class PassSecretsFactory extends SecretsFactory { public static boolean isBackendSupported() { try { - Process p = Runtime.getRuntime().exec("which pass"); + Process p = Runtime.getRuntime().exec(new String[] {"which", "pass"}); if (!p.waitFor(1000, TimeUnit.MILLISECONDS)) { return false; } diff --git a/src/main/java/zander/ui/library/LibraryCreateDialog.java b/src/main/java/zander/ui/library/LibraryCreateDialog.java index b38239f..7768425 100644 --- a/src/main/java/zander/ui/library/LibraryCreateDialog.java +++ b/src/main/java/zander/ui/library/LibraryCreateDialog.java @@ -12,6 +12,7 @@ import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JCheckBox; +import javax.swing.JComboBox; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JLabel; @@ -44,6 +45,8 @@ public class LibraryCreateDialog extends JDialog { private final JTextField usernameField; private final JLabel passwordLabel; private final JPasswordField passwordField; + private final JLabel ftpModeLabel; + private final JComboBox ftpModeCombo; private final JCheckBox defaultPathCheck; private final JLabel pathLabel; private final FileChooserField pathField; @@ -66,6 +69,8 @@ public class LibraryCreateDialog extends JDialog { usernameField = new JTextField(10); passwordLabel = new JLabel("Password:"); passwordField = new JPasswordField(10); + ftpModeLabel = new JLabel("FTP Mode:"); + ftpModeCombo = new JComboBox(new String[] { "Passive", "Active" }); defaultPathCheck = new JCheckBox("Use default library location"); defaultPathCheck.setSelected(true); pathLabel = new JLabel("Library Path:"); @@ -223,12 +228,21 @@ public class LibraryCreateDialog extends JDialog { entryPanel.add(passwordField, gbc); gbc.gridy = 5; 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.fill = GridBagConstraints.NONE; gbc.gridwidth = 2; entryPanel.add(defaultPathCheck, gbc); gbc.gridwidth = 1; - gbc.gridy = 6; + gbc.gridy = 7; gbc.gridx = 0; gbc.anchor = GridBagConstraints.LINE_END; entryPanel.add(pathLabel, gbc); @@ -266,4 +280,8 @@ public class LibraryCreateDialog extends JDialog { return pathField.getPathField().getText(); } + public boolean isFtpPassiveMode() { + return ftpModeCombo.getSelectedItem().equals("Passive"); + } + } diff --git a/src/main/java/zander/ui/library/LibraryEditDialog.java b/src/main/java/zander/ui/library/LibraryEditDialog.java index 92e9a58..852ed4d 100644 --- a/src/main/java/zander/ui/library/LibraryEditDialog.java +++ b/src/main/java/zander/ui/library/LibraryEditDialog.java @@ -12,6 +12,7 @@ import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JCheckBox; +import javax.swing.JComboBox; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JLabel; @@ -44,6 +45,8 @@ public class LibraryEditDialog extends JDialog { private final JTextField usernameField; private final JLabel passwordLabel; private final JPasswordField passwordField; + private final JLabel ftpModeLabel; + private final JComboBox ftpModeCombo; private final JCheckBox defaultPathCheck; private final JLabel pathLabel; private final FileChooserField pathField; @@ -55,7 +58,9 @@ public class LibraryEditDialog extends JDialog { private final JPanel buttonPanel; 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"); nameLabel = new JLabel("Name:"); nameField = new JTextField(10); @@ -72,6 +77,9 @@ public class LibraryEditDialog extends JDialog { passwordLabel = new JLabel("Password:"); passwordField = new JPasswordField(10); passwordField.setText(password); + ftpModeLabel = new JLabel("FTP Mode:"); + ftpModeCombo = new JComboBox(new String[] { "Passive", "Active" }); + ftpModeCombo.setSelectedIndex(passiveMode ? 0 : 1); defaultPathCheck = new JCheckBox("Use default library location"); pathLabel = new JLabel("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().setDialogTitle("Select Library Location"); nameField.getDocument().addDocumentListener(new DocumentListener(){ - @Override - public void insertUpdate(DocumentEvent e) { - update(); - } + @Override + public void insertUpdate(DocumentEvent e) { + update(); + } - @Override - public void removeUpdate(DocumentEvent e) { - update(); - } + @Override + public void removeUpdate(DocumentEvent e) { + update(); + } - @Override - public void changedUpdate(DocumentEvent e) { - update(); - } + @Override + public void changedUpdate(DocumentEvent e) { + update(); + } - private void update() { - if (defaultPathCheck.isSelected()) { - pathField.getPathField().setText(LibrarySelectFrame.DEFAULT_LIBRARY_PATH + "/" + nameField.getText()); - } - } - }); - defaultPathCheck.addItemListener((e) -> { + private void update() { if (defaultPathCheck.isSelected()) { 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()); buildEntryPanel(); cancelButton = new JButton("Cancel"); cancelButton.setMnemonic(KeyEvent.VK_C); cancelButton.addActionListener((e) -> { - dispose(); - }); + dispose(); + }); clearCacheButton = new JButton("Clear Cache"); clearCacheButton.setMnemonic(KeyEvent.VK_L); clearCacheButton.addActionListener((e) -> { - 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); - if (cacheClearCallback != null && c == JOptionPane.YES_OPTION) { - cacheClearCallback.run(); - } - }); + 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); + if (cacheClearCallback != null && c == JOptionPane.YES_OPTION) { + cacheClearCallback.run(); + } + }); helpButton = new JButton("?"); helpButton.addActionListener((e) -> DocumentationViewer.show(this, "Libraries/Local Library Settings")); saveButton = new JButton("Save"); saveButton.setMnemonic(KeyEvent.VK_S); saveButton.setEnabled(false); saveButton.addActionListener((e) -> { - if (nameField.getText().contains("/")) { - JOptionPane.showMessageDialog(this, "Library names cannot contain the '/' character!", "Library Error", JOptionPane.ERROR_MESSAGE); - } else { - dispose(); - response = RESPONSE_SAVE; - } - }); + if (nameField.getText().contains("/")) { + JOptionPane.showMessageDialog(this, "Library names cannot contain the '/' character!", "Library Error", JOptionPane.ERROR_MESSAGE); + } else { + dispose(); + response = RESPONSE_SAVE; + } + }); buttonPanel = new JPanel(); buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); buttonPanel.add(cancelButton); @@ -150,30 +158,30 @@ public class LibraryEditDialog extends JDialog { buttonPanel.add(clearCacheButton); buttonPanel.add(saveButton); final DocumentListener formFillListener = new DocumentListener(){ - @Override - public void insertUpdate(DocumentEvent e) { - update(); - } + @Override + public void insertUpdate(DocumentEvent e) { + update(); + } - @Override - public void removeUpdate(DocumentEvent e) { - update(); - } + @Override + public void removeUpdate(DocumentEvent e) { + update(); + } - @Override - public void changedUpdate(DocumentEvent e) { - update(); - } + @Override + public void changedUpdate(DocumentEvent e) { + update(); + } - private void update() { - if (!nameField.getText().isBlank() && !ftpField.getText().isBlank() - && !usernameField.getText().isBlank() && !pathField.getPathField().getText().isBlank()) { - saveButton.setEnabled(true); - } else { - saveButton.setEnabled(false); - } + private void update() { + if (!nameField.getText().isBlank() && !ftpField.getText().isBlank() + && !usernameField.getText().isBlank() && !pathField.getPathField().getText().isBlank()) { + saveButton.setEnabled(true); + } else { + saveButton.setEnabled(false); } - }; + } + }; nameField.getDocument().addDocumentListener(formFillListener); ftpField.getDocument().addDocumentListener(formFillListener); usernameField.getDocument().addDocumentListener(formFillListener); @@ -245,11 +253,20 @@ public class LibraryEditDialog extends JDialog { gbc.gridy = 5; 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.fill = GridBagConstraints.NONE; gbc.anchor = GridBagConstraints.CENTER; gbc.gridwidth = 2; entryPanel.add(defaultPathCheck, gbc); gbc.gridwidth = 1; - gbc.gridy = 6; + gbc.gridy = 7; gbc.gridx = 0; gbc.anchor = GridBagConstraints.LINE_END; entryPanel.add(pathLabel, gbc); @@ -287,4 +304,8 @@ public class LibraryEditDialog extends JDialog { return pathField.getPathField().getText(); } + public boolean isFtpPassiveMode() { + return ftpModeCombo.getSelectedItem().equals("Passive"); + } + } diff --git a/src/main/java/zander/ui/library/LibraryImportDialog.java b/src/main/java/zander/ui/library/LibraryImportDialog.java index 70dc3a4..30fee9d 100644 --- a/src/main/java/zander/ui/library/LibraryImportDialog.java +++ b/src/main/java/zander/ui/library/LibraryImportDialog.java @@ -10,6 +10,7 @@ import java.awt.event.KeyEvent; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JButton; +import javax.swing.JComboBox; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JLabel; @@ -39,6 +40,8 @@ public class LibraryImportDialog extends JDialog { private final JTextField usernameField; private final JLabel passwordLabel; private final JPasswordField passwordField; + private final JLabel ftpModeLabel; + private final JComboBox ftpModeCombo; private final JLabel fileLabel; private final FileChooserField fileField; private final JPanel entryPanel; @@ -58,6 +61,8 @@ public class LibraryImportDialog extends JDialog { usernameField = new JTextField(10); passwordLabel = new JLabel("Password:"); passwordField = new JPasswordField(10); + ftpModeLabel = new JLabel("FTP Mode:"); + ftpModeCombo = new JComboBox(new String[] { "Passive", "Active" }); fileLabel = new JLabel("Source:"); fileField = new FileChooserField(10); fileField.getChooser().setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); @@ -69,17 +74,17 @@ public class LibraryImportDialog extends JDialog { cancelButton = new JButton("Cancel"); cancelButton.setMnemonic(KeyEvent.VK_C); cancelButton.addActionListener((e) -> { - dispose(); - }); + dispose(); + }); helpButton = new JButton("?"); helpButton.addActionListener((e) -> DocumentationViewer.show(this, "Libraries/Local Library Settings")); importButton = new JButton("Import"); importButton.setMnemonic(KeyEvent.VK_I); importButton.setEnabled(false); importButton.addActionListener((e) -> { - dispose(); - response = RESPONSE_IMPORT; - }); + dispose(); + response = RESPONSE_IMPORT; + }); buttonPanel = new JPanel(); buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); buttonPanel.add(cancelButton); @@ -87,26 +92,26 @@ public class LibraryImportDialog extends JDialog { buttonPanel.add(helpButton); buttonPanel.add(importButton); final DocumentListener formFillListener = new DocumentListener(){ - @Override - public void insertUpdate(DocumentEvent e) { - update(); - } + @Override + public void insertUpdate(DocumentEvent e) { + update(); + } - @Override - public void removeUpdate(DocumentEvent e) { - update(); - } + @Override + public void removeUpdate(DocumentEvent e) { + update(); + } - @Override - public void changedUpdate(DocumentEvent e) { - update(); - } + @Override + public void changedUpdate(DocumentEvent e) { + update(); + } - private void update() { - importButton.setEnabled(!ftpField.getText().isBlank() && !usernameField.getText().isBlank() && !fileField.getPathField().getText().isBlank()); - } - }; + private void update() { + importButton.setEnabled(!ftpField.getText().isBlank() && !usernameField.getText().isBlank() && !fileField.getPathField().getText().isBlank()); + } + }; ftpField.getDocument().addDocumentListener(formFillListener); usernameField.getDocument().addDocumentListener(formFillListener); fileField.getPathField().getDocument().addDocumentListener(formFillListener); @@ -167,6 +172,15 @@ public class LibraryImportDialog extends JDialog { gbc.gridx = 0; gbc.anchor = GridBagConstraints.LINE_END; 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); gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; @@ -198,4 +212,8 @@ public class LibraryImportDialog extends JDialog { return fileField.getPathField().getText(); } + public boolean isFtpPassiveMode() { + return ftpModeCombo.getSelectedItem().equals("Passive"); + } + } diff --git a/src/main/java/zander/ui/library/LibrarySelectFrame.java b/src/main/java/zander/ui/library/LibrarySelectFrame.java index cd122cc..27333f9 100644 --- a/src/main/java/zander/ui/library/LibrarySelectFrame.java +++ b/src/main/java/zander/ui/library/LibrarySelectFrame.java @@ -80,12 +80,15 @@ public class LibrarySelectFrame extends JFrame { public String name; public String url; public String ftp; + public boolean passiveMode; 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.url = url; this.ftp = ftp; + this.passiveMode = passiveMode; this.file = file; } @@ -101,7 +104,9 @@ public class LibrarySelectFrame extends JFrame { public boolean equals(Object o) { if (o instanceof LibraryEntry) { 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; } @@ -201,7 +206,8 @@ public class LibrarySelectFrame extends JFrame { nld.setVisible(true); if (nld.getResponse() == LibraryCreateDialog.RESPONSE_CREATE) { 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"); @@ -209,7 +215,8 @@ public class LibrarySelectFrame extends JFrame { LibraryImportDialog lid = new LibraryImportDialog(this); lid.setVisible(true); 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(); @@ -287,7 +294,8 @@ public class LibrarySelectFrame extends JFrame { LibraryImportDialog lid = new LibraryImportDialog(this); lid.setVisible(true); 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"); @@ -297,7 +305,8 @@ public class LibrarySelectFrame extends JFrame { nld.setVisible(true); if (nld.getResponse() == LibraryCreateDialog.RESPONSE_CREATE) { 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"); @@ -468,7 +477,9 @@ public class LibrarySelectFrame extends JFrame { } private String formatLibraryUrl(String url) { - if (!url.matches("^https?\\:\\/\\/.*")) { + if (url.isBlank()) { + return ""; + } else if (!url.matches("^https?\\:\\/\\/.*")) { return "https://" + url; } return url; @@ -476,9 +487,10 @@ public class LibrarySelectFrame extends JFrame { private void editLibrary(LibraryEntry en) { Secrets s = SECRETS_FACTORY.createSecrets(SECRETS_KEY + en.name); - LibraryEditDialog led = new LibraryEditDialog(this, en.name, en.url, en.ftp, - s.getUsername(), s.getPassword(), en.file.getPath(), - () -> clearLibraryCache(en)); + LibraryEditDialog led = new LibraryEditDialog(this, en.name, en.url, + en.ftp, s.getUsername(), s.getPassword(), + en.passiveMode, en.file.getPath(), + () -> clearLibraryCache(en)); led.setVisible(true); if (led.getResponse() == LibraryEditDialog.RESPONSE_SAVE) { String testName = ""; @@ -506,6 +518,7 @@ public class LibrarySelectFrame extends JFrame { en.name = led.getLibraryName(); en.url = formatLibraryUrl(led.getURL()); en.ftp = led.getFTP(); + en.passiveMode = led.isFtpPassiveMode(); en.file = nf; saveLibraryList(); list.repaint(); @@ -548,7 +561,7 @@ public class LibrarySelectFrame extends JFrame { settingsPutObject("last-library", en); Secrets s = SECRETS_FACTORY.createSecrets(SECRETS_KEY + en.name); 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); if (lsd.sync() == LibrarySyncDialog.STATUS_OK) { 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; try { cf = dir.getCanonicalFile(); @@ -640,7 +654,7 @@ public class LibrarySelectFrame extends JFrame { Secrets s = SECRETS_FACTORY.createSecrets(SECRETS_KEY + name); s.setUsername(username); 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); openLibrary(en, false); } @@ -702,7 +716,8 @@ public class LibrarySelectFrame extends JFrame { 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; try { cf = file.getCanonicalFile(); @@ -726,7 +741,7 @@ public class LibrarySelectFrame extends JFrame { recursiveDelete(cf); break; case LibraryExistsDialog.RESPONSE_IMPORT: - importLibrary(url, ftp, username, password, file); + importLibrary(url, ftp, username, password, passiveMode, file); return; default: return; @@ -756,7 +771,7 @@ public class LibrarySelectFrame extends JFrame { Secrets s = SECRETS_FACTORY.createSecrets(SECRETS_KEY + name); s.setUsername(username); 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); openLibrary(en, false); } diff --git a/src/main/java/zander/ui/media/map/OSMLocationEncoder.java b/src/main/java/zander/ui/media/map/OSMLocationEncoder.java index 32be2b1..9a49e1e 100644 --- a/src/main/java/zander/ui/media/map/OSMLocationEncoder.java +++ b/src/main/java/zander/ui/media/map/OSMLocationEncoder.java @@ -3,6 +3,8 @@ package zander.ui.media.map; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.net.URLEncoder; @@ -128,9 +130,12 @@ public class OSMLocationEncoder implements LocationEncoder { private static URL getURLForQuery(String query) { try { - final String encodedQuery = URLEncoder.encode(query, StandardCharsets.UTF_8); - return new URL("https://nominatim.openstreetmap.org/search.php?format=jsonv2&q=" + encodedQuery); - } catch (MalformedURLException e) { + final String encodedQuery = URLEncoder.encode(query, + StandardCharsets.UTF_8); + 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); throw new Error("Could not create URL!", e); } diff --git a/src/main/resources/docs/library-create-dialog.png b/src/main/resources/docs/library-create-dialog.png index d9a0909..7238f5e 100644 Binary files a/src/main/resources/docs/library-create-dialog.png and b/src/main/resources/docs/library-create-dialog.png differ diff --git a/src/main/resources/docs/library-edit-dialog.png b/src/main/resources/docs/library-edit-dialog.png index 7a47b0e..93d6b92 100644 Binary files a/src/main/resources/docs/library-edit-dialog.png and b/src/main/resources/docs/library-edit-dialog.png differ diff --git a/src/main/resources/docs/library-import-dialog.png b/src/main/resources/docs/library-import-dialog.png index 11addc7..b0b62f2 100644 Binary files a/src/main/resources/docs/library-import-dialog.png and b/src/main/resources/docs/library-import-dialog.png differ diff --git a/src/main/resources/docs/local-library-settings.html b/src/main/resources/docs/local-library-settings.html index 6be9f2d..3aa4e31 100644 --- a/src/main/resources/docs/local-library-settings.html +++ b/src/main/resources/docs/local-library-settings.html @@ -4,18 +4,22 @@

Local Library Settings

Overview

- These dialogs create, import or alter the settings of libraries. These settings are primarily - for use of Curator and will not affect the Art Museum website. + These dialogs create, import or alter the settings of libraries. These + settings are primarily for use of Curator and will not affect the Art + Museum website.

Local vs Remote Library
- Remore libraries are instances of the Art Museum website. They are connected to by FTP. - Any changes made to them will be reflected in their respective Art Museum instance. + Remore libraries are instances of the Art Museum website. They are + connected to by FTP. Any changes made to them will be reflected in their + respective Art Museum instance.
- Local libraries, or caches, are instances of libraries located on your computer. When you connect - to a remote library, a local library is automatically created and sync with its remote counterpart. - Every time you connect to a remote library, its local counterpart is updated in this way. + Local libraries, or caches, are instances of libraries located on your + computer. When you connect to a remote library, a local library is + automatically created and sync with its remote counterpart. Every time + you connect to a remote library, its local counterpart is updated in this + way.

The library creation and remote import dialog.

@@ -29,16 +33,25 @@

The local library import dialog.

Options

    -
  • Name: library name in the select dialog. Does not affect website.
  • -
  • URL: url in the select dialog. Can be blank. Does not affect the website.
  • -
  • FTP: FTP server to connect to. Must be in the format of 'ftp://hostname:port/path/to/library'. - Protocol can also be FTPS by replacing 'ftp://' with 'ftps://'.
  • -
  • Username & Password: username and password to use to connect to FTP server.
  • -
  • Library Location (Source): 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. +
  • Name: library name in the select dialog. Does not affect + website.
  • +
  • URL: url in the select dialog. Can be blank. Does not affect + the website.
  • +
  • FTP: FTP server to connect to. Must be in the format of + 'ftp://hostname:port/path/to/library'. Encryption is supported via + FTPS. To enable it, replace 'ftp://' with 'ftps://'.
  • +
  • Username & Password: username and password to use to connect to + FTP server.
  • +
  • FTP Mode: 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.
  • +
  • Library Location (Source): 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. See: Global Settings
  • -
  • Clear Cahce: clear library cahce. This does NOT affect 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 +
  • Clear Cahce: clear library cahce. This does NOT affect + 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.