From: David ‘Bombe’ Roden Date: Tue, 28 Aug 2012 08:52:01 +0000 (+0200) Subject: Merge branch 'mavenize' into next X-Git-Tag: 0.11^2~19 X-Git-Url: https://git.pterodactylus.net/?a=commitdiff_plain;h=9e6e70c28963fde2a6c8b120974f8a70d4da25bc;p=jSite.git Merge branch 'mavenize' into next --- 9e6e70c28963fde2a6c8b120974f8a70d4da25bc diff --cc src/main/java/de/todesbaum/jsite/application/KeyDialog.java index 0000000,4b64f72..cd628e7 mode 000000,100644..100644 --- a/src/main/java/de/todesbaum/jsite/application/KeyDialog.java +++ b/src/main/java/de/todesbaum/jsite/application/KeyDialog.java @@@ -1,0 -1,318 +1,322 @@@ + /* + * jSite - KeyDialog.java - Copyright © 2010–2012 David Roden + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + package de.todesbaum.jsite.application; + + import java.awt.BorderLayout; + import java.awt.Dimension; + import java.awt.FlowLayout; + import java.awt.GridBagConstraints; + import java.awt.GridBagLayout; + import java.awt.Insets; + import java.awt.Toolkit; + import java.awt.event.ActionEvent; + import java.awt.event.InputEvent; + import java.awt.event.KeyEvent; + import java.awt.event.WindowAdapter; + import java.awt.event.WindowEvent; + import java.io.IOException; + import java.text.MessageFormat; + + import javax.swing.AbstractAction; + import javax.swing.Action; + import javax.swing.BorderFactory; + import javax.swing.JButton; + import javax.swing.JDialog; + import javax.swing.JFrame; + import javax.swing.JLabel; + import javax.swing.JOptionPane; + import javax.swing.JPanel; + import javax.swing.JSeparator; + import javax.swing.JTextField; + import javax.swing.KeyStroke; + import javax.swing.SwingConstants; + + import de.todesbaum.jsite.i18n.I18n; + import de.todesbaum.jsite.i18n.I18nContainer; + + /** + * A dialog that lets the user edit the private and public key for a project. + * + * @author David ‘Bombe’ Roden <bombe@freenetproject.org> + */ + public class KeyDialog extends JDialog { + + /** Interface to the freenet node. */ + private final Freenet7Interface freenetInterface; + + /** The public key. */ + private String publicKey; + + /** The private key. */ + private String privateKey; + + /** The “OK” button’s action. */ + private Action okAction; + + /** The “Cancel” button’s action. */ + private Action cancelAction; + + /** The “Regenerate” button’s action. */ + private Action generateAction; + + /** The text field for the private key. */ + private JTextField privateKeyTextField; + + /** The text field for the public key. */ + private JTextField publicKeyTextField; + + /** Whether the dialog was cancelled. */ + private boolean cancelled; + + /** + * Creates a new key dialog. + * + * @param freenetInterface + * Interface to the freenet node + * @param parent + * The parent frame + */ + public KeyDialog(Freenet7Interface freenetInterface, JFrame parent) { + super(parent, I18n.getMessage("jsite.key-dialog.title"), true); + this.freenetInterface = freenetInterface; + addWindowListener(new WindowAdapter() { + + @Override + @SuppressWarnings("synthetic-access") + public void windowClosing(WindowEvent windowEvent) { + actionCancel(); + } + }); + initDialog(); + } + + // + // ACCESSORS + // + + /** + * Returns whether the dialog was cancelled. + * + * @return {@code true} if the dialog was cancelled, {@code false} otherwise + */ + public boolean wasCancelled() { + return cancelled; + } + + /** + * Returns the public key. + * + * @return The public key + */ + public String getPublicKey() { + return publicKey; + } + + /** + * Sets the public key. + * + * @param publicKey + * The public key + */ + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + publicKeyTextField.setText(publicKey); + pack(); + } + + /** + * Returns the private key. + * + * @return The private key + */ + public String getPrivateKey() { + return privateKey; + } + + /** + * Sets the private key. + * + * @param privateKey + * The private key + */ + public void setPrivateKey(String privateKey) { + this.privateKey = privateKey; + privateKeyTextField.setText(privateKey); + pack(); + } + + // + // ACTIONS + // + + /** + * {@inheritDoc} + */ + @Override + public void pack() { + super.pack(); + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + setLocation((screenSize.width - getWidth()) / 2, (screenSize.height - getHeight()) / 2); + } + + // + // PRIVATE METHODS + // + + /** + * Creates all necessary actions. + */ + private void createActions() { + okAction = new AbstractAction(I18n.getMessage("jsite.general.ok")) { + ++ @Override + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + actionOk(); + } + }; + okAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.key-dialog.button.ok.tooltip")); + okAction.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_ENTER); + + cancelAction = new AbstractAction(I18n.getMessage("jsite.general.cancel")) { + ++ @Override + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + actionCancel(); + } + }; + cancelAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.key-dialog.button.cancel.tooltip")); + cancelAction.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_ESCAPE); + + generateAction = new AbstractAction(I18n.getMessage("jsite.key-dialog.button.generate")) { + ++ @Override + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + actionGenerate(); + } + }; + generateAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.key-dialog.button.generate.tooltip")); + generateAction.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.CTRL_DOWN_MASK)); + } + + /** + * Initializes the dialog and all its components. + */ + private void initDialog() { + createActions(); + JPanel dialogPanel = new JPanel(new BorderLayout(12, 12)); + dialogPanel.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12)); + + JPanel contentPanel = new JPanel(new GridBagLayout()); + dialogPanel.add(contentPanel, BorderLayout.CENTER); + + final JLabel keysLabel = new JLabel(I18n.getMessage("jsite.key-dialog.label.keys")); + contentPanel.add(keysLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); + + final JLabel privateKeyLabel = new JLabel(I18n.getMessage("jsite.key-dialog.label.private-key")); + contentPanel.add(privateKeyLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(12, 18, 0, 0), 0, 0)); + + privateKeyTextField = new JTextField(); + contentPanel.add(privateKeyTextField, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(6, 12, 0, 0), 0, 0)); + + final JLabel publicKeyLabel = new JLabel(I18n.getMessage("jsite.key-dialog.label.public-key")); + contentPanel.add(publicKeyLabel, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 18, 0, 0), 0, 0)); + + publicKeyTextField = new JTextField(); + contentPanel.add(publicKeyTextField, new GridBagConstraints(1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(6, 12, 0, 0), 0, 0)); + + final JLabel actionsLabel = new JLabel(I18n.getMessage("jsite.key-dialog.label.actions")); + contentPanel.add(actionsLabel, new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(12, 0, 0, 0), 0, 0)); + + JPanel actionButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 12, 12)); + actionButtonPanel.setBorder(BorderFactory.createEmptyBorder(-12, -12, -12, -12)); + contentPanel.add(actionButtonPanel, new GridBagConstraints(0, 4, 2, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(12, 18, 0, 0), 0, 0)); + + actionButtonPanel.add(new JButton(generateAction)); + + JPanel separatorPanel = new JPanel(new BorderLayout(12, 12)); + dialogPanel.add(separatorPanel, BorderLayout.PAGE_END); + separatorPanel.add(new JSeparator(SwingConstants.HORIZONTAL), BorderLayout.PAGE_START); + + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING, 12, 12)); + buttonPanel.setBorder(BorderFactory.createEmptyBorder(-12, -12, -12, -12)); + separatorPanel.add(buttonPanel, BorderLayout.CENTER); + buttonPanel.add(new JButton(okAction)); + buttonPanel.add(new JButton(cancelAction)); + + I18nContainer.getInstance().registerRunnable(new Runnable() { + ++ @Override + public void run() { + keysLabel.setText(I18n.getMessage("jsite.key-dialog.label.keys")); + privateKeyLabel.setText(I18n.getMessage("jsite.key-dialog.label.private-key")); + publicKeyLabel.setText(I18n.getMessage("jsite.key-dialog.label.public-key")); + actionsLabel.setText(I18n.getMessage("jsite.key-dialog.label.actions")); + } + }); + + getContentPane().add(dialogPanel, BorderLayout.CENTER); + pack(); + setResizable(false); + } + + // + // PRIVATE ACTIONS + // + + /** + * Quits the dialog, accepting all changes. + */ + private void actionOk() { + publicKey = publicKeyTextField.getText(); + privateKey = privateKeyTextField.getText(); + cancelled = false; + setVisible(false); + } + + /** + * Quits the dialog, discarding all changes. + */ + private void actionCancel() { + cancelled = true; + setVisible(false); + } + + /** + * Generates a new key pair. + */ + private void actionGenerate() { + if (JOptionPane.showConfirmDialog(this, I18n.getMessage("jsite.project.warning.generate-new-key"), null, JOptionPane.OK_CANCEL_OPTION) == JOptionPane.CANCEL_OPTION) { + return; + } + String[] keyPair = null; + try { + keyPair = freenetInterface.generateKeyPair(); + } catch (IOException ioe1) { + JOptionPane.showMessageDialog(this, MessageFormat.format(I18n.getMessage("jsite.project.keygen.io-error"), ioe1.getMessage()), null, JOptionPane.ERROR_MESSAGE); + return; + } + publicKeyTextField.setText(keyPair[1].substring(keyPair[1].indexOf('@') + 1, keyPair[1].lastIndexOf('/'))); + privateKeyTextField.setText(keyPair[0].substring(keyPair[0].indexOf('@') + 1, keyPair[0].lastIndexOf('/'))); + pack(); + } + + } diff --cc src/main/java/de/todesbaum/jsite/application/Project.java index 0000000,33d8447..36fff55 mode 000000,100644..100644 --- a/src/main/java/de/todesbaum/jsite/application/Project.java +++ b/src/main/java/de/todesbaum/jsite/application/Project.java @@@ -1,0 -1,441 +1,442 @@@ + /* + * jSite - Project.java - Copyright © 2006–2012 David Roden + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + package de.todesbaum.jsite.application; + + import java.io.File; + import java.util.Collections; + import java.util.HashMap; + import java.util.Map; + import java.util.Map.Entry; + + import net.pterodactylus.util.io.MimeTypes; + + /** + * Container for project information. + * + * @author David ‘Bombe’ Roden <bombe@freenetproject.org> + */ + public class Project implements Comparable { + + /** The name of the project. */ + protected String name; + + /** The description of the project. */ + protected String description; + + /** The insert URI of the project. */ + protected String insertURI; + + /** The request URI of the project. */ + protected String requestURI; + + /** The index file of the project. */ + protected String indexFile; + + /** The local path of the project. */ + protected String localPath; + + /** The remote path of the URI. */ + protected String path; + + /** The time of the last insertion. */ + protected long lastInsertionTime; + + /** The edition to insert to. */ + protected int edition; + + /** Whether to ignore hidden directory. */ + private boolean ignoreHiddenFiles; + + /** Options for files. */ + protected Map fileOptions = new HashMap(); + + /** + * Empty constructor. + */ + public Project() { + /* do nothing. */ + } + + /** + * Creates a new project from an existing one. + * + * @param project + * The project to clone + */ + public Project(Project project) { + name = project.name; + description = project.description; + insertURI = project.insertURI; + requestURI = project.requestURI; + path = project.path; + edition = project.edition; + localPath = project.localPath; + indexFile = project.indexFile; + lastInsertionTime = project.lastInsertionTime; + ignoreHiddenFiles = project.ignoreHiddenFiles; + fileOptions = new HashMap(project.fileOptions); + } + + /** + * Returns the name of the project. + * + * @return The name of the project + */ + public String getName() { + return name; + } + + /** + * Sets the name of the project. + * + * @param name + * The name of the project + */ + public void setName(String name) { + this.name = name; + } + + /** + * Returns the description of the project. + * + * @return The description of the project + */ + public String getDescription() { + return description; + } + + /** + * Sets the description of the project. + * + * @param description + * The description of the project + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * Returns the local path of the project. + * + * @return The local path of the project + */ + public String getLocalPath() { + return localPath; + } + + /** + * Sets the local path of the project. + * + * @param localPath + * The local path of the project + */ + public void setLocalPath(String localPath) { + this.localPath = localPath; + } + + /** + * Returns the name of the index file of the project, relative to the + * project’s local path. + * + * @return The name of the index file of the project + */ + public String getIndexFile() { + return indexFile; + } + + /** + * Sets the name of the index file of the project, relative to the project’s + * local path. + * + * @param indexFile + * The name of the index file of the project + */ + public void setIndexFile(String indexFile) { + this.indexFile = indexFile; + } + + /** + * Returns the time the project was last inserted, in milliseconds since the + * epoch. + * + * @return The time of the last insertion + */ + public long getLastInsertionTime() { + return lastInsertionTime; + } + + /** + * Sets the time the project was last inserted, in milliseconds since the + * last epoch. + * + * @param lastInserted + * The time of the last insertion + */ + public void setLastInsertionTime(long lastInserted) { + lastInsertionTime = lastInserted; + } + + /** + * Returns the remote path of the project. The remote path is the path that + * directly follows the request URI of the project. + * + * @return The remote path of the project + */ + public String getPath() { + return path; + } + + /** + * Sets the remote path of the project. The remote path is the path that + * directly follows the request URI of the project. + * + * @param path + * The remote path of the project + */ + public void setPath(String path) { + this.path = path; + } + + /** + * Returns the insert URI of the project. + * + * @return The insert URI of the project + */ + public String getInsertURI() { + return insertURI; + } + + /** + * Sets the insert URI of the project. + * + * @param insertURI + * The insert URI of the project + */ + public void setInsertURI(String insertURI) { + this.insertURI = shortenURI(insertURI); + } + + /** + * Returns the request URI of the project. + * + * @return The request URI of the project + */ + public String getRequestURI() { + return requestURI; + } + + /** + * Sets the request URI of the project. + * + * @param requestURI + * The request URI of the project + */ + public void setRequestURI(String requestURI) { + this.requestURI = shortenURI(requestURI); + } + + /** + * Returns whether hidden files are ignored, i.e. not inserted. + * + * @return {@code true} if hidden files are not inserted, {@code false} + * otherwise + */ + public boolean isIgnoreHiddenFiles() { + return ignoreHiddenFiles; + } + + /** + * Sets whether hidden files are ignored, i.e. not inserted. + * + * @param ignoreHiddenFiles + * {@code true} if hidden files are not inserted, {@code false} + * otherwise + */ + public void setIgnoreHiddenFiles(boolean ignoreHiddenFiles) { + this.ignoreHiddenFiles = ignoreHiddenFiles; + } + + /** + * {@inheritDoc} + *

+ * This method returns the name of the project. + */ + @Override + public String toString() { + return name; + } + + /** + * Shortens the given URI by removing scheme and key-type prefixes. + * + * @param uri + * The URI to shorten + * @return The shortened URI + */ - private String shortenURI(String uri) { ++ private static String shortenURI(String uri) { + String shortUri = uri; + if (shortUri.startsWith("freenet:")) { + shortUri = shortUri.substring("freenet:".length()); + } + if (shortUri.startsWith("SSK@")) { + shortUri = shortUri.substring("SSK@".length()); + } + if (shortUri.startsWith("USK@")) { + shortUri = shortUri.substring("USK@".length()); + } + if (shortUri.endsWith("/")) { + shortUri = shortUri.substring(0, shortUri.length() - 1); + } + return shortUri; + } + + /** + * Shortens the name of the given file by removing the local path of the + * project and leading file separators. + * + * @param file + * The file whose name should be shortened + * @return The shortened name of the file + */ + public String shortenFilename(File file) { + String filename = file.getPath(); + if (filename.startsWith(localPath)) { + filename = filename.substring(localPath.length()); + if (filename.startsWith(File.separator)) { + filename = filename.substring(1); + } + } + return filename; + } + + /** + * Returns the options for the file with the given name. If the file does + * not yet have any options, a new set of default options is created and + * returned. + * + * @param filename + * The name of the file, relative to the project root + * @return The options for the file + */ + public FileOption getFileOption(String filename) { + FileOption fileOption = fileOptions.get(filename); + if (fileOption == null) { + fileOption = new FileOption(MimeTypes.getMimeType(filename.substring(filename.lastIndexOf('.') + 1))); + fileOptions.put(filename, fileOption); + } + return fileOption; + } + + /** + * Sets options for a file. + * + * @param filename + * The filename to set the options for, relative to the project + * root + * @param fileOption + * The options to set for the file, or null to + * remove the options for the file + */ + public void setFileOption(String filename, FileOption fileOption) { + if (fileOption != null) { + fileOptions.put(filename, fileOption); + } else { + fileOptions.remove(filename); + } + } + + /** + * Returns all file options. + * + * @return All file options + */ + public Map getFileOptions() { + return Collections.unmodifiableMap(fileOptions); + } + + /** + * Sets all file options. + * + * @param fileOptions + * The file options + */ + public void setFileOptions(Map fileOptions) { + this.fileOptions.clear(); + this.fileOptions.putAll(fileOptions); + } + + /** + * {@inheritDoc} + *

+ * Projects are compared by their name only. + */ ++ @Override + public int compareTo(Project project) { + return name.compareToIgnoreCase(project.name); + } + + /** + * Returns the edition of the project. + * + * @return The edition of the project + */ + public int getEdition() { + return edition; + } + + /** + * Sets the edition of the project. + * + * @param edition + * The edition to set + */ + public void setEdition(int edition) { + this.edition = edition; + } + + /** + * Constructs the final request URI including the edition number. + * + * @param offset + * The offset for the edition number + * @return The final request URI + */ + public String getFinalRequestURI(int offset) { + return "USK@" + requestURI + "/" + path + "/" + (edition + offset) + "/"; + } + + /** + * Performs some post-processing on the project after it was inserted + * successfully. At the moment it copies the current hashes of all file + * options to the last insert hashes, updating the hashes for the next + * insert. + */ + public void onSuccessfulInsert() { + for (Entry fileOptionEntry : fileOptions.entrySet()) { + FileOption fileOption = fileOptionEntry.getValue(); + if ((fileOption.getCurrentHash() != null) && (fileOption.getCurrentHash().length() > 0) && (!fileOption.getCurrentHash().equals(fileOption.getLastInsertHash()) || fileOption.isForceInsert())) { + fileOption.setLastInsertEdition(edition); + fileOption.setLastInsertHash(fileOption.getCurrentHash()); + fileOption.setLastInsertFilename(fileOption.hasChangedName() ? fileOption.getChangedName() : fileOptionEntry.getKey()); + } + fileOption.setForceInsert(false); + } + } + + } diff --cc src/main/java/de/todesbaum/jsite/application/ProjectInserter.java index 0000000,a9ea8d1..ed06533 mode 000000,100644..100644 --- a/src/main/java/de/todesbaum/jsite/application/ProjectInserter.java +++ b/src/main/java/de/todesbaum/jsite/application/ProjectInserter.java @@@ -1,0 -1,694 +1,697 @@@ + /* + * jSite - ProjectInserter.java - Copyright © 2006–2012 David Roden + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + package de.todesbaum.jsite.application; + + import java.io.File; + import java.io.FileInputStream; + import java.io.IOException; + import java.io.InputStream; + import java.util.ArrayList; + import java.util.Arrays; + import java.util.HashSet; + import java.util.Iterator; + import java.util.List; + import java.util.Map; + import java.util.Map.Entry; + import java.util.Set; + import java.util.concurrent.CountDownLatch; + import java.util.logging.Level; + import java.util.logging.Logger; + + import net.pterodactylus.util.io.StreamCopier.ProgressListener; + import de.todesbaum.jsite.gui.FileScanner; + import de.todesbaum.jsite.gui.FileScanner.ScannedFile; + import de.todesbaum.jsite.gui.FileScannerListener; + import de.todesbaum.util.freenet.fcp2.Client; + import de.todesbaum.util.freenet.fcp2.ClientPutComplexDir; + import de.todesbaum.util.freenet.fcp2.ClientPutDir.ManifestPutter; + import de.todesbaum.util.freenet.fcp2.Connection; + import de.todesbaum.util.freenet.fcp2.DirectFileEntry; + import de.todesbaum.util.freenet.fcp2.FileEntry; + import de.todesbaum.util.freenet.fcp2.Message; + import de.todesbaum.util.freenet.fcp2.PriorityClass; + import de.todesbaum.util.freenet.fcp2.RedirectFileEntry; + import de.todesbaum.util.freenet.fcp2.Verbosity; + + /** + * Manages project inserts. + * + * @author David ‘Bombe’ Roden <bombe@freenetproject.org> + */ + public class ProjectInserter implements FileScannerListener, Runnable { + + /** The logger. */ + private static final Logger logger = Logger.getLogger(ProjectInserter.class.getName()); + + /** Random number for FCP instances. */ + private static final int random = (int) (Math.random() * Integer.MAX_VALUE); + + /** Counter for FCP connection identifier. */ + private static int counter = 0; + + /** The list of insert listeners. */ + private List insertListeners = new ArrayList(); + + /** The freenet interface. */ + protected Freenet7Interface freenetInterface; + + /** The project to insert. */ + protected Project project; + + /** The file scanner. */ + private FileScanner fileScanner; + + /** Object used for synchronization. */ + protected final Object lockObject = new Object(); + + /** The temp directory. */ + private String tempDirectory; + + /** The current connection. */ + private Connection connection; + + /** Whether the insert is cancelled. */ + private volatile boolean cancelled = false; + + /** Progress listener for payload transfers. */ + private ProgressListener progressListener; + + /** Whether to use “early encode.” */ + private boolean useEarlyEncode; + + /** The insert priority. */ + private PriorityClass priority; + + /** The manifest putter. */ + private ManifestPutter manifestPutter; + + /** + * Adds a listener to the list of registered listeners. + * + * @param insertListener + * The listener to add + */ + public void addInsertListener(InsertListener insertListener) { + insertListeners.add(insertListener); + } + + /** + * Removes a listener from the list of registered listeners. + * + * @param insertListener + * The listener to remove + */ + public void removeInsertListener(InsertListener insertListener) { + insertListeners.remove(insertListener); + } + + /** + * Notifies all listeners that the project insert has started. + * + * @see InsertListener#projectInsertStarted(Project) + */ + protected void fireProjectInsertStarted() { + for (InsertListener insertListener : insertListeners) { + insertListener.projectInsertStarted(project); + } + } + + /** + * Notifies all listeners that the insert has generated a URI. + * + * @see InsertListener#projectURIGenerated(Project, String) + * @param uri + * The generated URI + */ + protected void fireProjectURIGenerated(String uri) { + for (InsertListener insertListener : insertListeners) { + insertListener.projectURIGenerated(project, uri); + } + } + + /** + * Notifies all listeners that the insert has made some progress. + * + * @see InsertListener#projectUploadFinished(Project) + */ + protected void fireProjectUploadFinished() { + for (InsertListener insertListener : insertListeners) { + insertListener.projectUploadFinished(project); + } + } + + /** + * Notifies all listeners that the insert has made some progress. + * + * @see InsertListener#projectInsertProgress(Project, int, int, int, int, + * boolean) + * @param succeeded + * The number of succeeded blocks + * @param failed + * The number of failed blocks + * @param fatal + * The number of fatally failed blocks + * @param total + * The total number of blocks + * @param finalized + * true if the total number of blocks has already + * been finalized, false otherwise + */ + protected void fireProjectInsertProgress(int succeeded, int failed, int fatal, int total, boolean finalized) { + for (InsertListener insertListener : insertListeners) { + insertListener.projectInsertProgress(project, succeeded, failed, fatal, total, finalized); + } + } + + /** + * Notifies all listeners the project insert has finished. + * + * @see InsertListener#projectInsertFinished(Project, boolean, Throwable) + * @param success + * true if the project was inserted successfully, + * false if it failed + * @param cause + * The cause of the failure, if any + */ + protected void fireProjectInsertFinished(boolean success, Throwable cause) { + for (InsertListener insertListener : insertListeners) { + insertListener.projectInsertFinished(project, success, cause); + } + } + + /** + * Sets the project to insert. + * + * @param project + * The project to insert + */ + public void setProject(Project project) { + this.project = project; + } + + /** + * Sets the freenet interface to use. + * + * @param freenetInterface + * The freenet interface to use + */ + public void setFreenetInterface(Freenet7Interface freenetInterface) { + this.freenetInterface = freenetInterface; + } + + /** + * Sets the temp directory to use. + * + * @param tempDirectory + * The temp directory to use, or {@code null} to use the system + * default + */ + public void setTempDirectory(String tempDirectory) { + this.tempDirectory = tempDirectory; + } + + /** + * Sets whether to use the “early encode“ flag for the insert. + * + * @param useEarlyEncode + * {@code true} to set the “early encode” flag for the insert, + * {@code false} otherwise + */ + public void setUseEarlyEncode(boolean useEarlyEncode) { + this.useEarlyEncode = useEarlyEncode; + } + + /** + * Sets the insert priority. + * + * @param priority + * The insert priority + */ + public void setPriority(PriorityClass priority) { + this.priority = priority; + } + + /** + * Sets the manifest putter to use for inserts. + * + * @param manifestPutter + * The manifest putter to use + */ + public void setManifestPutter(ManifestPutter manifestPutter) { + this.manifestPutter = manifestPutter; + } + + /** + * Starts the insert. + * + * @param progressListener + * Listener to notify on progress events + */ + public void start(ProgressListener progressListener) { + cancelled = false; + this.progressListener = progressListener; + fileScanner = new FileScanner(project); + fileScanner.addFileScannerListener(this); + new Thread(fileScanner).start(); + } + + /** + * Stops the current insert. + */ + public void stop() { + cancelled = true; + synchronized (lockObject) { + if (connection != null) { + connection.disconnect(); + } + } + } + + /** + * Creates an input stream that delivers the given file, replacing edition + * tokens in the file’s content, if necessary. + * + * @param filename + * The name of the file + * @param fileOption + * The file options + * @param edition + * The current edition + * @param length + * An array containing a single long which is used to + * return the final length of the file, after all + * replacements + * @return The input stream for the file + * @throws IOException + * if an I/O error occurs + */ + private InputStream createFileInputStream(String filename, FileOption fileOption, int edition, long[] length) throws IOException { + File file = new File(project.getLocalPath(), filename); + length[0] = file.length(); + return new FileInputStream(file); + } + + /** + * Creates a file entry suitable for handing in to + * {@link ClientPutComplexDir#addFileEntry(FileEntry)}. + * + * @param file + * The name and hash of the file to insert + * @param edition + * The current edition + * @return A file entry for the given file + */ + private FileEntry createFileEntry(ScannedFile file, int edition) { + FileEntry fileEntry = null; + String filename = file.getFilename(); + FileOption fileOption = project.getFileOption(filename); + if (fileOption.isInsert()) { + fileOption.setCurrentHash(file.getHash()); + /* check if file was modified. */ + if (!fileOption.isForceInsert() && file.getHash().equals(fileOption.getLastInsertHash())) { + /* only insert a redirect. */ + logger.log(Level.FINE, String.format("Inserting redirect to edition %d for %s.", fileOption.getLastInsertEdition(), filename)); + return new RedirectFileEntry(fileOption.hasChangedName() ? fileOption.getChangedName() : filename, fileOption.getMimeType(), "SSK@" + project.getRequestURI() + "/" + project.getPath() + "-" + fileOption.getLastInsertEdition() + "/" + fileOption.getLastInsertFilename()); + } + try { + long[] fileLength = new long[1]; + InputStream fileEntryInputStream = createFileInputStream(filename, fileOption, edition, fileLength); + fileEntry = new DirectFileEntry(fileOption.hasChangedName() ? fileOption.getChangedName() : filename, fileOption.getMimeType(), fileEntryInputStream, fileLength[0]); + } catch (IOException ioe1) { + /* ignore, null is returned. */ + } + } else { + if (fileOption.isInsertRedirect()) { + fileEntry = new RedirectFileEntry(fileOption.hasChangedName() ? fileOption.getChangedName() : filename, fileOption.getMimeType(), fileOption.getCustomKey()); + } + } + return fileEntry; + } + + /** + * Validates the given project. The project will be checked for any invalid + * conditions, such as invalid insert or request keys, missing path names, + * missing default file, and so on. + * + * @param project + * The project to check + * @return The encountered warnings and errors + */ + public static CheckReport validateProject(Project project) { + CheckReport checkReport = new CheckReport(); + if ((project.getLocalPath() == null) || (project.getLocalPath().trim().length() == 0)) { + checkReport.addIssue("error.no-local-path", true); + } + if ((project.getPath() == null) || (project.getPath().trim().length() == 0)) { + checkReport.addIssue("error.no-path", true); + } + if ((project.getIndexFile() == null) || (project.getIndexFile().length() == 0)) { + checkReport.addIssue("warning.empty-index", false); + } else { + File indexFile = new File(project.getLocalPath(), project.getIndexFile()); + if (!indexFile.exists()) { + checkReport.addIssue("error.index-missing", true); + } + } + String indexFile = project.getIndexFile(); + boolean hasIndexFile = (indexFile != null) && (indexFile.length() > 0); + List allowedIndexContentTypes = Arrays.asList("text/html", "application/xhtml+xml"); + if (hasIndexFile && !allowedIndexContentTypes.contains(project.getFileOption(indexFile).getMimeType())) { + checkReport.addIssue("warning.index-not-html", false); + } + Map fileOptions = project.getFileOptions(); + Set> fileOptionEntries = fileOptions.entrySet(); + boolean insert = fileOptionEntries.isEmpty(); + for (Entry fileOptionEntry : fileOptionEntries) { + String fileName = fileOptionEntry.getKey(); + FileOption fileOption = fileOptionEntry.getValue(); + insert |= fileOption.isInsert() || fileOption.isInsertRedirect(); + if (fileName.equals(project.getIndexFile()) && !fileOption.isInsert() && !fileOption.isInsertRedirect()) { + checkReport.addIssue("error.index-not-inserted", true); + } + if (!fileOption.isInsert() && fileOption.isInsertRedirect() && ((fileOption.getCustomKey().length() == 0) || "CHK@".equals(fileOption.getCustomKey()))) { + checkReport.addIssue("error.no-custom-key", true, fileName); + } + } + if (!insert) { + checkReport.addIssue("error.no-files-to-insert", true); + } + Set fileNames = new HashSet(); + for (Entry fileOptionEntry : fileOptionEntries) { + FileOption fileOption = fileOptionEntry.getValue(); + if (!fileOption.isInsert() && !fileOption.isInsertRedirect()) { + logger.log(Level.FINEST, "Ignoring {0}.", fileOptionEntry.getKey()); + continue; + } + String fileName = fileOptionEntry.getKey(); + if (fileOption.hasChangedName()) { + fileName = fileOption.getChangedName(); + } + logger.log(Level.FINEST, "Adding “{0}” for {1}.", new Object[] { fileName, fileOptionEntry.getKey() }); + if (!fileNames.add(fileName)) { + checkReport.addIssue("error.duplicate-file", true, fileName); + } + } + long totalSize = 0; + FileScanner fileScanner = new FileScanner(project); + final CountDownLatch completionLatch = new CountDownLatch(1); + fileScanner.addFileScannerListener(new FileScannerListener() { + + @Override + public void fileScannerFinished(FileScanner fileScanner) { + completionLatch.countDown(); + } + }); + new Thread(fileScanner).start(); + while (completionLatch.getCount() > 0) { + try { + completionLatch.await(); + } catch (InterruptedException ie1) { + /* TODO: logging */ + } + } + for (ScannedFile scannedFile : fileScanner.getFiles()) { + String fileName = scannedFile.getFilename(); + FileOption fileOption = project.getFileOption(fileName); + if ((fileOption != null) && !fileOption.isInsert()) { + continue; + } + totalSize += new File(project.getLocalPath(), fileName).length(); + } + if (totalSize > 2 * 1024 * 1024) { + checkReport.addIssue("warning.site-larger-than-2-mib", false); + } + return checkReport; + } + + /** + * {@inheritDoc} + */ ++ @Override + public void run() { + fireProjectInsertStarted(); + List files = fileScanner.getFiles(); + + /* create connection to node */ + synchronized (lockObject) { + connection = freenetInterface.getConnection("project-insert-" + random + counter++); + } + connection.setTempDirectory(tempDirectory); + boolean connected = false; + Throwable cause = null; + try { + connected = connection.connect(); + } catch (IOException e1) { + cause = e1; + } + + if (!connected || cancelled) { + fireProjectInsertFinished(false, cancelled ? new AbortedException() : cause); + return; + } + + Client client = new Client(connection); + + /* collect files */ + int edition = project.getEdition(); + String dirURI = "USK@" + project.getInsertURI() + "/" + project.getPath() + "/" + edition + "/"; + ClientPutComplexDir putDir = new ClientPutComplexDir("dir-" + counter++, dirURI, tempDirectory); + if ((project.getIndexFile() != null) && (project.getIndexFile().length() > 0)) { + putDir.setDefaultName(project.getIndexFile()); + } + putDir.setVerbosity(Verbosity.ALL); + putDir.setMaxRetries(-1); + putDir.setEarlyEncode(useEarlyEncode); + putDir.setPriorityClass(priority); + putDir.setManifestPutter(manifestPutter); + for (ScannedFile file : files) { + FileEntry fileEntry = createFileEntry(file, edition); + if (fileEntry != null) { + try { + putDir.addFileEntry(fileEntry); + } catch (IOException ioe1) { + fireProjectInsertFinished(false, ioe1); + return; + } + } + } + + /* start request */ + try { + client.execute(putDir, progressListener); + fireProjectUploadFinished(); + } catch (IOException ioe1) { + fireProjectInsertFinished(false, ioe1); + return; + } + + /* parse progress and success messages */ + String finalURI = null; + boolean success = false; + boolean finished = false; + boolean disconnected = false; + while (!finished && !cancelled) { + Message message = client.readMessage(); + finished = (message == null) || (disconnected = client.isDisconnected()); + logger.log(Level.FINE, "Received message: " + message); + if (!finished) { + @SuppressWarnings("null") + String messageName = message.getName(); + if ("URIGenerated".equals(messageName)) { + finalURI = message.get("URI"); + fireProjectURIGenerated(finalURI); + } + if ("SimpleProgress".equals(messageName)) { + int total = Integer.parseInt(message.get("Total")); + int succeeded = Integer.parseInt(message.get("Succeeded")); + int fatal = Integer.parseInt(message.get("FatallyFailed")); + int failed = Integer.parseInt(message.get("Failed")); + boolean finalized = Boolean.parseBoolean(message.get("FinalizedTotal")); + fireProjectInsertProgress(succeeded, failed, fatal, total, finalized); + } + success |= "PutSuccessful".equals(messageName); + finished = (success && (finalURI != null)) || "PutFailed".equals(messageName) || messageName.endsWith("Error"); + } + } + + /* post-insert work */ + if (success) { + @SuppressWarnings("null") + String editionPart = finalURI.substring(finalURI.lastIndexOf('/') + 1); + int newEdition = Integer.parseInt(editionPart); + project.setEdition(newEdition); + project.setLastInsertionTime(System.currentTimeMillis()); + project.onSuccessfulInsert(); + } + fireProjectInsertFinished(success, cancelled ? new AbortedException() : (disconnected ? new IOException("Connection terminated") : null)); + } + + // + // INTERFACE FileScannerListener + // + + /** + * {@inheritDoc} + */ ++ @Override + public void fileScannerFinished(FileScanner fileScanner) { + if (!fileScanner.isError()) { + new Thread(this).start(); + } else { + fireProjectInsertFinished(false, null); + } + fileScanner.removeFileScannerListener(this); + } + + /** + * Container class that collects all warnings and errors that occured during + * {@link ProjectInserter#validateProject(Project) project validation}. + * + * @author David ‘Bombe’ Roden + */ + public static class CheckReport implements Iterable { + + /** The issures that occured. */ + private final List issues = new ArrayList(); + + /** + * Adds an issue. + * + * @param issue + * The issue to add + */ + public void addIssue(Issue issue) { + issues.add(issue); + } + + /** + * Creates an {@link Issue} from the given error key and fatality flag + * and {@link #addIssue(Issue) adds} it. + * + * @param errorKey + * The error key + * @param fatal + * {@code true} if the error is fatal, {@code false} if only + * a warning should be generated + * @param parameters + * Any additional parameters + */ + public void addIssue(String errorKey, boolean fatal, String... parameters) { + addIssue(new Issue(errorKey, fatal, parameters)); + } + + /** + * {@inheritDoc} + */ ++ @Override + public Iterator iterator() { + return issues.iterator(); + } + + /** + * Returns whether this check report does not contain any errors. + * + * @return {@code true} if this check report does not contain any + * errors, {@code false} if this check report does contain + * errors + */ + public boolean isEmpty() { + return issues.isEmpty(); + } + + /** + * Returns the number of issues in this check report. + * + * @return The number of issues + */ + public int size() { + return issues.size(); + } + + } + + /** + * Container class for a single issue. An issue contains an error key + * that describes the error, and a fatality flag that determines whether + * the insert has to be aborted (if the flag is {@code true}) or if it + * can still be performed and only a warning should be generated (if the + * flag is {@code false}). + * + * @author David ‘Bombe’ + * Roden + */ + public static class Issue { + + /** The error key. */ + private final String errorKey; + + /** The fatality flag. */ + private final boolean fatal; + + /** Additional parameters. */ + private String[] parameters; + + /** + * Creates a new issue. + * + * @param errorKey + * The error key + * @param fatal + * The fatality flag + * @param parameters + * Any additional parameters + */ + protected Issue(String errorKey, boolean fatal, String... parameters) { + this.errorKey = errorKey; + this.fatal = fatal; + this.parameters = parameters; + } + + /** + * Returns the key of the encountered error. + * + * @return The error key + */ + public String getErrorKey() { + return errorKey; + } + + /** + * Returns whether the issue is fatal and the insert has to be + * aborted. Otherwise only a warning should be shown. + * + * @return {@code true} if the insert needs to be aborted, {@code + * false} otherwise + */ + public boolean isFatal() { + return fatal; + } + + /** + * Returns any additional parameters. + * + * @return The additional parameters + */ + public String[] getParameters() { + return parameters; + } + + } + + } diff --cc src/main/java/de/todesbaum/jsite/application/UpdateChecker.java index 0000000,1f55681..bd3b14f mode 000000,100644..100644 --- a/src/main/java/de/todesbaum/jsite/application/UpdateChecker.java +++ b/src/main/java/de/todesbaum/jsite/application/UpdateChecker.java @@@ -1,0 -1,287 +1,288 @@@ + /* + * jSite - UpdateChecker.java - Copyright © 2008–2012 David Roden + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + package de.todesbaum.jsite.application; + + import java.io.IOException; + import java.io.InputStream; + import java.util.ArrayList; + import java.util.List; + import java.util.Properties; + import java.util.logging.Level; + import java.util.logging.Logger; + + import net.pterodactylus.util.io.Closer; + import de.todesbaum.jsite.main.Main; + import de.todesbaum.jsite.main.Version; + import de.todesbaum.util.freenet.fcp2.Client; + import de.todesbaum.util.freenet.fcp2.ClientGet; + import de.todesbaum.util.freenet.fcp2.Connection; + import de.todesbaum.util.freenet.fcp2.Message; + import de.todesbaum.util.freenet.fcp2.Persistence; + import de.todesbaum.util.freenet.fcp2.ReturnType; + import de.todesbaum.util.freenet.fcp2.Verbosity; + + /** + * Checks for newer versions of jSite. + * + * @author David ‘Bombe’ Roden <bombe@freenetproject.org> + */ + public class UpdateChecker implements Runnable { + + /** The logger. */ + private static final Logger logger = Logger.getLogger(UpdateChecker.class.getName()); + + /** Counter for connection names. */ + private static int counter = 0; + + /** The edition for the update check URL. */ + private static final int UPDATE_EDITION = 17; + + /** The URL for update checks. */ + private static final String UPDATE_KEY = "USK@e3myoFyp5avg6WYN16ImHri6J7Nj8980Fm~aQe4EX1U,QvbWT0ImE0TwLODTl7EoJx2NBnwDxTbLTE6zkB-eGPs,AQACAAE"; + + /** Object used for synchronization. */ + private final Object syncObject = new Object(); + + /** Update listeners. */ + private final List updateListeners = new ArrayList(); + + /** Whether the main thread should stop. */ + private boolean shouldStop = false; + + /** Current last found edition of update key. */ + private int lastUpdateEdition = UPDATE_EDITION; + + /** Last found version. */ + private Version lastVersion = Main.getVersion(); + + /** The freenet interface. */ + private final Freenet7Interface freenetInterface; + + /** + * Creates a new update checker that uses the given frame as its parent and + * communications via the given freenet interface. + * + * @param freenetInterface + * The freenet interface + */ + public UpdateChecker(Freenet7Interface freenetInterface) { + this.freenetInterface = freenetInterface; + } + + // + // EVENT LISTENER MANAGEMENT + // + + /** + * Adds an update listener to the list of registered listeners. + * + * @param updateListener + * The update listener to add + */ + public void addUpdateListener(UpdateListener updateListener) { + updateListeners.add(updateListener); + } + + /** + * Removes the given listener from the list of registered listeners. + * + * @param updateListener + * The update listener to remove + */ + public void removeUpdateListener(UpdateListener updateListener) { + updateListeners.remove(updateListener); + } + + /** + * Notifies all listeners that a version was found. + * + * @param foundVersion + * The version that was found + * @param versionTimestamp + * The timestamp of the version + */ + protected void fireUpdateFound(Version foundVersion, long versionTimestamp) { + for (UpdateListener updateListener : updateListeners) { + updateListener.foundUpdateData(foundVersion, versionTimestamp); + } + } + + // + // ACCESSORS + // + + /** + * Returns the latest version that was found. + * + * @return The latest found version + */ + public Version getLatestVersion() { + return lastVersion; + } + + // + // ACTIONS + // + + /** + * Starts the update checker. + */ + public void start() { + new Thread(this).start(); + } + + /** + * Stops the update checker. + */ + public void stop() { + synchronized (syncObject) { + shouldStop = true; + syncObject.notifyAll(); + } + } + + // + // PRIVATE METHODS + // + + /** + * Returns whether the update checker should stop. + * + * @return true if the update checker should stop, + * false otherwise + */ + private boolean shouldStop() { + synchronized (syncObject) { + return shouldStop; + } + } + + /** + * Creates the URI of the update file for the given edition. + * + * @param edition + * The edition number + * @return The URI for the update file for the given edition + */ - private String constructUpdateKey(int edition) { ++ private static String constructUpdateKey(int edition) { + return UPDATE_KEY + "/jSite/" + edition + "/jSite.properties"; + } + + // + // INTERFACE Runnable + // + + /** + * {@inheritDoc} + */ ++ @Override + public void run() { + Connection connection = freenetInterface.getConnection("jSite-" + ++counter + "-UpdateChecker"); + try { + connection.connect(); + } catch (IOException e1) { + e1.printStackTrace(); + } + Client client = new Client(connection); + boolean checkNow = false; + int currentEdition = lastUpdateEdition; + while (!shouldStop()) { + checkNow = false; + logger.log(Level.FINE, "Trying " + constructUpdateKey(currentEdition)); + ClientGet clientGet = new ClientGet("get-update-key"); + clientGet.setUri(constructUpdateKey(currentEdition)); + clientGet.setPersistence(Persistence.CONNECTION); + clientGet.setReturnType(ReturnType.direct); + clientGet.setVerbosity(Verbosity.ALL); + try { + client.execute(clientGet); + boolean stop = false; + while (!stop) { + Message message = client.readMessage(); + logger.log(Level.FINEST, "Received message: " + message); + if (message == null) { + break; + } + if ("GetFailed".equals(message.getName())) { + if ("27".equals(message.get("code"))) { + String editionString = message.get("redirecturi").split("/")[2]; + int editionNumber = -1; + try { + editionNumber = Integer.parseInt(editionString); + } catch (NumberFormatException nfe1) { + /* ignore. */ + } + if (editionNumber != -1) { + logger.log(Level.INFO, "Found new edition " + editionNumber); + currentEdition = editionNumber; + lastUpdateEdition = editionNumber; + checkNow = true; + break; + } + } + } + if ("AllData".equals(message.getName())) { + logger.log(Level.FINE, "Update data found."); + InputStream dataInputStream = null; + Properties properties = new Properties(); + try { + dataInputStream = message.getPayloadInputStream(); + properties.load(dataInputStream); + } finally { + Closer.close(dataInputStream); + } + + String foundVersionString = properties.getProperty("jSite.Version"); + if (foundVersionString != null) { + Version foundVersion = Version.parse(foundVersionString); + if (foundVersion != null) { + lastVersion = foundVersion; + String versionTimestampString = properties.getProperty("jSite.Date"); + logger.log(Level.FINEST, "Version timestamp: " + versionTimestampString); + long versionTimestamp = -1; + try { + versionTimestamp = Long.parseLong(versionTimestampString); + } catch (NumberFormatException nfe1) { + /* ignore. */ + } + fireUpdateFound(foundVersion, versionTimestamp); + stop = true; + checkNow = true; + ++currentEdition; + } + } + } + } + } catch (IOException e) { + logger.log(Level.INFO, "Got IOException: " + e.getMessage()); + e.printStackTrace(); + } + if (!checkNow && !shouldStop()) { + synchronized (syncObject) { + try { + syncObject.wait(15 * 60 * 1000); + } catch (InterruptedException ie1) { + /* ignore. */ + } + } + } + } + } + + } diff --cc src/main/java/de/todesbaum/jsite/gui/FileScanner.java index 0000000,aa07943..bd98ed0 mode 000000,100644..100644 --- a/src/main/java/de/todesbaum/jsite/gui/FileScanner.java +++ b/src/main/java/de/todesbaum/jsite/gui/FileScanner.java @@@ -1,0 -1,344 +1,347 @@@ + /* + * jSite - FileScanner.java - Copyright © 2006–2012 David Roden + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + package de.todesbaum.jsite.gui; + + import java.io.File; + import java.io.FileFilter; + import java.io.FileInputStream; + import java.io.IOException; + import java.io.InputStream; + import java.io.OutputStream; + import java.security.DigestOutputStream; + import java.security.MessageDigest; + import java.security.NoSuchAlgorithmException; + import java.util.ArrayList; + import java.util.Collections; + import java.util.List; + import java.util.logging.Level; + import java.util.logging.Logger; + + import net.pterodactylus.util.io.Closer; + import net.pterodactylus.util.io.StreamCopier; + import de.todesbaum.jsite.application.Project; + import de.todesbaum.jsite.i18n.I18n; + + /** + * Scans the local path of a project anychronously and returns the list of found + * files as an event. + * + * @see Project#getLocalPath() + * @see FileScannerListener#fileScannerFinished(FileScanner) + * @author David ‘Bombe’ Roden <bombe@freenetproject.org> + */ + public class FileScanner implements Runnable { + + /** The logger. */ + private final static Logger logger = Logger.getLogger(FileScanner.class.getName()); + + /** The list of listeners. */ + private final List fileScannerListeners = new ArrayList(); + + /** The project to scan. */ + private final Project project; + + /** The list of found files. */ + private List files; + + /** Wether there was an error. */ + private boolean error = false; + + /** + * Creates a new file scanner for the given project. + * + * @param project + * The project whose files to scan + */ + public FileScanner(Project project) { + this.project = project; + } + + /** + * Adds the given listener to the list of listeners. + * + * @param fileScannerListener + * The listener to add + */ + public void addFileScannerListener(FileScannerListener fileScannerListener) { + fileScannerListeners.add(fileScannerListener); + } + + /** + * Removes the given listener from the list of listeners. + * + * @param fileScannerListener + * The listener to remove + */ + public void removeFileScannerListener(FileScannerListener fileScannerListener) { + fileScannerListeners.remove(fileScannerListener); + } + + /** + * Notifies all listeners that the file scan finished. + */ + protected void fireFileScannerFinished() { + for (FileScannerListener fileScannerListener : new ArrayList(fileScannerListeners)) { + fileScannerListener.fileScannerFinished(this); + } + } + + /** + * {@inheritDoc} + *

+ * Scans all available files in the project’s local path and emits an event + * when finished. + * + * @see FileScannerListener#fileScannerFinished(FileScanner) + */ ++ @Override + public void run() { + files = new ArrayList(); + error = false; + try { + scanFiles(new File(project.getLocalPath()), files); + Collections.sort(files); + } catch (IOException ioe1) { + error = true; + } + fireFileScannerFinished(); + } + + /** + * Returns whether there was an error scanning for files. + * + * @return true if there was an error, false + * otherwise + */ + public boolean isError() { + return error; + } + + /** + * Returns the list of found files. + * + * @return The list of found files + */ + public List getFiles() { + return files; + } + + /** + * Recursively scans a directory and adds all found files to the given list. + * + * @param rootDir + * The directory to scan + * @param fileList + * The list to which to add the found files + * @throws IOException + * if an I/O error occurs + */ + private void scanFiles(File rootDir, List fileList) throws IOException { + File[] files = rootDir.listFiles(new FileFilter() { + ++ @Override + @SuppressWarnings("synthetic-access") + public boolean accept(File file) { + return !project.isIgnoreHiddenFiles() || !file.isHidden(); + } + }); + if (files == null) { + throw new IOException(I18n.getMessage("jsite.file-scanner.can-not-read-directory")); + } + for (File file : files) { + if (file.isDirectory()) { + scanFiles(file, fileList); + continue; + } + String filename = project.shortenFilename(file).replace('\\', '/'); + String hash = hashFile(project.getLocalPath(), filename); + fileList.add(new ScannedFile(filename, hash)); + } + } + + /** + * Hashes the given file. + * + * @param path + * The path of the project + * @param filename + * The name of the file, relative to the project path + * @return The hash of the file + */ + @SuppressWarnings("synthetic-access") + private static String hashFile(String path, String filename) { + InputStream fileInputStream = null; + DigestOutputStream digestOutputStream = null; + File file = new File(path, filename); + try { + fileInputStream = new FileInputStream(file); + digestOutputStream = new DigestOutputStream(new NullOutputStream(), MessageDigest.getInstance("SHA-256")); + StreamCopier.copy(fileInputStream, digestOutputStream, file.length()); + return toHex(digestOutputStream.getMessageDigest().digest()); + } catch (NoSuchAlgorithmException nsae1) { + logger.log(Level.WARNING, "Could not get SHA-256 digest!", nsae1); + } catch (IOException ioe1) { + logger.log(Level.WARNING, "Could not read file!", ioe1); + } finally { + Closer.close(digestOutputStream); + Closer.close(fileInputStream); + } + return toHex(new byte[32]); + } + + /** + * Converts the given byte array into a hexadecimal string. + * + * @param array + * The array to convert + * @return The hexadecimal string + */ + private static String toHex(byte[] array) { + StringBuilder hexString = new StringBuilder(array.length * 2); + for (byte b : array) { + hexString.append("0123456789abcdef".charAt((b >>> 4) & 0x0f)).append("0123456789abcdef".charAt(b & 0xf)); + } + return hexString.toString(); + } + + /** + * {@link OutputStream} that discards all written bytes. + * + * @author David ‘Bombe’ Roden <bombe@freenetproject.org> + */ + private static class NullOutputStream extends OutputStream { + + /** + * {@inheritDoc} + */ + @Override + public void write(int b) { + /* do nothing. */ + } + + /** + * {@inheritDoc} + */ + @Override + public void write(byte[] b) { + /* do nothing. */ + } + + /** + * {@inheritDoc} + */ + @Override + public void write(byte[] b, int off, int len) { + /* do nothing. */ + } + + } + + /** + * Container for a scanned file, consisting of the name of the file and its + * hash. + * + * @author David ‘Bombe’ Roden <bombe@freenetproject.org> + */ + public static class ScannedFile implements Comparable { + + /** The name of the file. */ + private final String filename; + + /** The hash of the file. */ + private final String hash; + + /** + * Creates a new scanned file. + * + * @param filename + * The name of the file + * @param hash + * The hash of the file + */ + public ScannedFile(String filename, String hash) { + this.filename = filename; + this.hash = hash; + } + + // + // ACCESSORS + // + + /** + * Returns the name of the file. + * + * @return The name of the file + */ + public String getFilename() { + return filename; + } + + /** + * Returns the hash of the file. + * + * @return The hash of the file + */ + public String getHash() { + return hash; + } + + // + // OBJECT METHODS + // + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return filename.hashCode(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + return filename.equals(obj); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return filename; + } + + // + // COMPARABLE METHODS + // + + /** + * {@inheritDoc} + */ ++ @Override + public int compareTo(ScannedFile scannedFile) { + return filename.compareTo(scannedFile.filename); + } + + } + + } diff --cc src/main/java/de/todesbaum/jsite/gui/NodeManagerPage.java index 0000000,8e44129..49c25bc mode 000000,100644..100644 --- a/src/main/java/de/todesbaum/jsite/gui/NodeManagerPage.java +++ b/src/main/java/de/todesbaum/jsite/gui/NodeManagerPage.java @@@ -1,0 -1,450 +1,460 @@@ + /* + * jSite - NodeManagerPage.java - Copyright © 2006–2012 David Roden + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + package de.todesbaum.jsite.gui; + + import java.awt.BorderLayout; + import java.awt.Dimension; + import java.awt.FlowLayout; + import java.awt.GridBagConstraints; + import java.awt.GridBagLayout; + import java.awt.Insets; + import java.awt.event.ActionEvent; + import java.awt.event.KeyEvent; + import java.util.ArrayList; + import java.util.List; + + import javax.swing.AbstractAction; + import javax.swing.Action; + import javax.swing.DefaultListModel; + import javax.swing.JButton; + import javax.swing.JLabel; + import javax.swing.JList; + import javax.swing.JOptionPane; + import javax.swing.JPanel; + import javax.swing.JScrollPane; + import javax.swing.JSpinner; + import javax.swing.JTextField; + import javax.swing.ListSelectionModel; + import javax.swing.SpinnerNumberModel; + import javax.swing.border.EmptyBorder; + import javax.swing.event.ChangeEvent; + import javax.swing.event.ChangeListener; + import javax.swing.event.DocumentEvent; + import javax.swing.event.DocumentListener; + import javax.swing.event.ListSelectionEvent; + import javax.swing.event.ListSelectionListener; + import javax.swing.text.BadLocationException; + import javax.swing.text.Document; + + import de.todesbaum.jsite.application.Node; + import de.todesbaum.jsite.i18n.I18n; + import de.todesbaum.jsite.i18n.I18nContainer; + import de.todesbaum.util.swing.TLabel; + import de.todesbaum.util.swing.TWizard; + import de.todesbaum.util.swing.TWizardPage; + + /** + * Wizard page that lets the user edit his nodes. + * + * @author David ‘Bombe’ Roden <bombe@freenetproject.org> + */ + public class NodeManagerPage extends TWizardPage implements ListSelectionListener, DocumentListener, ChangeListener { + + /** List of node manager listeners. */ + private List nodeManagerListeners = new ArrayList(); + + /** The “add node” action. */ + protected Action addNodeAction; + + /** The “delete node” action. */ + protected Action deleteNodeAction; + + /** The node list model. */ + private DefaultListModel nodeListModel; + + /** The node list. */ + private JList nodeList; + + /** The node name textfield. */ + private JTextField nodeNameTextField; + + /** The node hostname textfield. */ + private JTextField nodeHostnameTextField; + + /** The spinner for the node port. */ + private JSpinner nodePortSpinner; + + /** + * Creates a new node manager wizard page. + * + * @param wizard + * The wizard this page belongs to + */ + public NodeManagerPage(final TWizard wizard) { + super(wizard); + pageInit(); + setHeading(I18n.getMessage("jsite.node-manager.heading")); + setDescription(I18n.getMessage("jsite.node-manager.description")); + I18nContainer.getInstance().registerRunnable(new Runnable() { + ++ @Override + public void run() { + setHeading(I18n.getMessage("jsite.node-manager.heading")); + setDescription(I18n.getMessage("jsite.node-manager.description")); + } + }); + } + + /** + * Adds a listener for node manager events. + * + * @param nodeManagerListener + * The listener to add + */ + public void addNodeManagerListener(NodeManagerListener nodeManagerListener) { + nodeManagerListeners.add(nodeManagerListener); + } + + /** + * Removes a listener for node manager events. + * + * @param nodeManagerListener + * The listener to remove + */ + public void removeNodeManagerListener(NodeManagerListener nodeManagerListener) { + nodeManagerListeners.remove(nodeManagerListener); + } + + /** + * Notifies all listeners that the node configuration has changed. + * + * @param nodes + * The new list of nodes + */ + protected void fireNodesUpdated(Node[] nodes) { + for (NodeManagerListener nodeManagerListener : nodeManagerListeners) { + nodeManagerListener.nodesUpdated(nodes); + } + } + + /** + * Notifies all listeners that a new node was selected. + * + * @param node + * The newly selected node + */ + protected void fireNodeSelected(Node node) { + for (NodeManagerListener nodeManagerListener : nodeManagerListeners) { + nodeManagerListener.nodeSelected(node); + } + } + + /** + * Creates all actions. + */ + private void createActions() { + addNodeAction = new AbstractAction(I18n.getMessage("jsite.node-manager.add-node")) { + ++ @Override + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + addNode(); + } + }; + + deleteNodeAction = new AbstractAction(I18n.getMessage("jsite.node-manager.delete-node")) { + ++ @Override + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + deleteNode(); + } + }; + deleteNodeAction.setEnabled(false); + + I18nContainer.getInstance().registerRunnable(new Runnable() { + ++ @Override + public void run() { + addNodeAction.putValue(Action.NAME, I18n.getMessage("jsite.node-manager.add-node")); + deleteNodeAction.putValue(Action.NAME, I18n.getMessage("jsite.node-manager.delete-node")); + } + }); + } + + /** + * Initializes the page and all components in it. + */ + private void pageInit() { + createActions(); + nodeListModel = new DefaultListModel(); + nodeList = new JList(nodeListModel); + nodeList.setName("node-list"); + nodeList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + nodeList.addListSelectionListener(this); + nodeList.setPreferredSize(new Dimension(250, -1)); + + nodeNameTextField = new JTextField(""); + nodeNameTextField.getDocument().putProperty("Name", "node-name"); + nodeNameTextField.getDocument().addDocumentListener(this); + nodeNameTextField.setEnabled(false); + + nodeHostnameTextField = new JTextField("localhost"); + nodeHostnameTextField.getDocument().putProperty("Name", "node-hostname"); + nodeHostnameTextField.getDocument().addDocumentListener(this); + nodeHostnameTextField.setEnabled(false); + + nodePortSpinner = new JSpinner(new SpinnerNumberModel(9481, 1, 65535, 1)); + nodePortSpinner.setName("node-port"); + nodePortSpinner.addChangeListener(this); + nodePortSpinner.setEnabled(false); + + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 12, 12)); + buttonPanel.setBorder(new EmptyBorder(-12, -12, -12, -12)); + buttonPanel.add(new JButton(addNodeAction)); + buttonPanel.add(new JButton(deleteNodeAction)); + + JPanel centerPanel = new JPanel(new BorderLayout()); + JPanel nodeInformationPanel = new JPanel(new GridBagLayout()); + centerPanel.add(nodeInformationPanel, BorderLayout.PAGE_START); + nodeInformationPanel.add(buttonPanel, new GridBagConstraints(0, 0, 2, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); + final JLabel nodeInformationLabel = new JLabel("" + I18n.getMessage("jsite.node-manager.node-information") + ""); + nodeInformationPanel.add(nodeInformationLabel, new GridBagConstraints(0, 1, 2, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 0, 0, 0), 0, 0)); + final TLabel nodeNameLabel = new TLabel(I18n.getMessage("jsite.node-manager.name") + ":", KeyEvent.VK_N, nodeNameTextField); + nodeInformationPanel.add(nodeNameLabel, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 18, 0, 0), 0, 0)); + nodeInformationPanel.add(nodeNameTextField, new GridBagConstraints(1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0)); + final TLabel nodeHostnameLabel = new TLabel(I18n.getMessage("jsite.node-manager.hostname") + ":", KeyEvent.VK_H, nodeHostnameTextField); + nodeInformationPanel.add(nodeHostnameLabel, new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 18, 0, 0), 0, 0)); + nodeInformationPanel.add(nodeHostnameTextField, new GridBagConstraints(1, 3, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0)); + final TLabel nodePortLabel = new TLabel(I18n.getMessage("jsite.node-manager.port") + ":", KeyEvent.VK_P, nodePortSpinner); + nodeInformationPanel.add(nodePortLabel, new GridBagConstraints(0, 4, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 18, 0, 0), 0, 0)); + nodeInformationPanel.add(nodePortSpinner, new GridBagConstraints(1, 4, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 6, 0, 0), 0, 0)); + + setLayout(new BorderLayout(12, 12)); + add(new JScrollPane(nodeList), BorderLayout.LINE_START); + add(centerPanel, BorderLayout.CENTER); + + I18nContainer.getInstance().registerRunnable(new Runnable() { + ++ @Override + public void run() { + nodeInformationLabel.setText("" + I18n.getMessage("jsite.node-manager.node-information") + ""); + nodeNameLabel.setText(I18n.getMessage("jsite.node-manager.name") + ":"); + nodeHostnameLabel.setText(I18n.getMessage("jsite.node-manager.hostname") + ":"); + nodePortLabel.setText(I18n.getMessage("jsite.node-manager.port") + ":"); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public void pageAdded(TWizard wizard) { + this.wizard.setNextEnabled(nodeListModel.getSize() > 0); + this.wizard.setPreviousName(I18n.getMessage("jsite.wizard.previous")); + this.wizard.setNextName(I18n.getMessage("jsite.wizard.next")); + this.wizard.setQuitName(I18n.getMessage("jsite.wizard.quit")); + } + + /** + * Sets the node list. + * + * @param nodes + * The list of nodes + */ + public void setNodes(Node[] nodes) { + nodeListModel.clear(); + for (Node node : nodes) { + nodeListModel.addElement(node); + } + nodeList.repaint(); + fireNodesUpdated(nodes); + } + + /** + * Returns the node list. + * + * @return The list of nodes + */ + public Node[] getNodes() { + Node[] returnNodes = new Node[nodeListModel.getSize()]; + for (int nodeIndex = 0, nodeCount = nodeListModel.getSize(); nodeIndex < nodeCount; nodeIndex++) { + returnNodes[nodeIndex] = (Node) nodeListModel.get(nodeIndex); + } + return returnNodes; + } + + /** + * Returns the currently selected node. + * + * @return The selected node, or null if no node is selected + */ + private Node getSelectedNode() { + return (Node) nodeList.getSelectedValue(); + } + + /** + * Updates node name or hostname when the user types into the textfields. + * + * @see #insertUpdate(DocumentEvent) + * @see #removeUpdate(DocumentEvent) + * @see #changedUpdate(DocumentEvent) + * @see DocumentListener + * @param documentEvent + * The document event + */ + private void updateTextField(DocumentEvent documentEvent) { + Node node = getSelectedNode(); + if (node == null) { + return; + } + Document document = documentEvent.getDocument(); + String documentText = null; + try { + documentText = document.getText(0, document.getLength()); + } catch (BadLocationException ble1) { + /* ignore. */ + } + if (documentText == null) { + return; + } + String documentName = (String) document.getProperty("Name"); + if ("node-name".equals(documentName)) { + node.setName(documentText); + nodeList.repaint(); + fireNodesUpdated(getNodes()); + } else if ("node-hostname".equals(documentName)) { + node.setHostname(documentText); + nodeList.repaint(); + fireNodesUpdated(getNodes()); + } + } + + // + // ACTIONS + // + + /** + * Adds a new node to the list of nodes. + */ + private void addNode() { + Node node = new Node("localhost", 9481, I18n.getMessage("jsite.node-manager.new-node")); + nodeListModel.addElement(node); + deleteNodeAction.setEnabled(nodeListModel.size() > 1); + wizard.setNextEnabled(true); + fireNodesUpdated(getNodes()); + } + + /** + * Deletes the currently selected node from the list of nodes. + */ + private void deleteNode() { + Node node = getSelectedNode(); + if (node == null) { + return; + } + if (JOptionPane.showConfirmDialog(wizard, I18n.getMessage("jsite.node-manager.delete-node.warning"), null, JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE) == JOptionPane.CANCEL_OPTION) { + return; + } + int nodeIndex = nodeListModel.indexOf(node); + nodeListModel.removeElement(node); + nodeList.repaint(); + fireNodeSelected((Node) nodeListModel.get(Math.min(nodeIndex, nodeListModel.size() - 1))); + fireNodesUpdated(getNodes()); + deleteNodeAction.setEnabled(nodeListModel.size() > 1); + wizard.setNextEnabled(nodeListModel.size() > 0); + } + + // + // INTERFACE ListSelectionListener + // + + /** + * {@inheritDoc} + */ ++ @Override + @SuppressWarnings("null") + public void valueChanged(ListSelectionEvent e) { + Object source = e.getSource(); + if (source instanceof JList) { + JList sourceList = (JList) source; + if ("node-list".equals(sourceList.getName())) { + Node node = (Node) sourceList.getSelectedValue(); + boolean enabled = (node != null); + nodeNameTextField.setEnabled(enabled); + nodeHostnameTextField.setEnabled(enabled); + nodePortSpinner.setEnabled(enabled); + deleteNodeAction.setEnabled(enabled && (nodeListModel.size() > 1)); + if (enabled) { + nodeNameTextField.setText(node.getName()); + nodeHostnameTextField.setText(node.getHostname()); + nodePortSpinner.setValue(node.getPort()); + } else { + nodeNameTextField.setText(""); + nodeHostnameTextField.setText("localhost"); + nodePortSpinner.setValue(9481); + } + } + } + } + + // + // INTERFACE DocumentListener + // + + /** + * {@inheritDoc} + */ ++ @Override + public void insertUpdate(DocumentEvent e) { + updateTextField(e); + } + + /** + * {@inheritDoc} + */ ++ @Override + public void removeUpdate(DocumentEvent e) { + updateTextField(e); + } + + /** + * {@inheritDoc} + */ ++ @Override + public void changedUpdate(DocumentEvent e) { + updateTextField(e); + } + + // + // INTERFACE ChangeListener + // + + /** + * {@inheritDoc} + */ ++ @Override + public void stateChanged(ChangeEvent e) { + Object source = e.getSource(); + Node selectedNode = getSelectedNode(); + if (selectedNode == null) { + return; + } + if (source instanceof JSpinner) { + JSpinner sourceSpinner = (JSpinner) source; + if ("node-port".equals(sourceSpinner.getName())) { + selectedNode.setPort((Integer) sourceSpinner.getValue()); + fireNodeSelected(selectedNode); + nodeList.repaint(); + } + } + } + + } diff --cc src/main/java/de/todesbaum/jsite/gui/PreferencesPage.java index 0000000,5cf258a..914e74b mode 000000,100644..100644 --- a/src/main/java/de/todesbaum/jsite/gui/PreferencesPage.java +++ b/src/main/java/de/todesbaum/jsite/gui/PreferencesPage.java @@@ -1,0 -1,519 +1,530 @@@ + /* + * jSite - PreferencesPage.java - Copyright © 2009–2012 David Roden + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + package de.todesbaum.jsite.gui; + + import java.awt.BorderLayout; + import java.awt.GridBagConstraints; + import java.awt.GridBagLayout; + import java.awt.Insets; + import java.awt.event.ActionEvent; + + import javax.swing.AbstractAction; + import javax.swing.Action; + import javax.swing.BorderFactory; + import javax.swing.ButtonGroup; + import javax.swing.JButton; + import javax.swing.JCheckBox; + import javax.swing.JComboBox; + import javax.swing.JFileChooser; + import javax.swing.JLabel; + import javax.swing.JPanel; + import javax.swing.JRadioButton; + import javax.swing.JTextField; + + import de.todesbaum.jsite.i18n.I18n; + import de.todesbaum.jsite.i18n.I18nContainer; + import de.todesbaum.jsite.main.ConfigurationLocator.ConfigurationLocation; + import de.todesbaum.util.freenet.fcp2.ClientPutDir.ManifestPutter; + import de.todesbaum.util.freenet.fcp2.PriorityClass; + import de.todesbaum.util.swing.TWizard; + import de.todesbaum.util.swing.TWizardPage; + + /** + * Page that shows some preferences that are valid for the complete application. + * + * @author David ‘Bombe’ Roden <bombe@freenetproject.org> + */ + public class PreferencesPage extends TWizardPage { + + /** Select default temp directory action. */ + private Action selectDefaultTempDirectoryAction; + + /** Select custom temp directory action. */ + private Action selectCustomTempDirectoryAction; + + /** Action that chooses a new temp directory. */ + private Action chooseTempDirectoryAction; + + /** Action when selecting “next to JAR file.” */ + private Action nextToJarFileAction; + + /** Action when selecting “home directory.” */ + private Action homeDirectoryAction; + + /** Action when selecting “custom directory.” */ + private Action customDirectoryAction; + + /** Action when selecting “use early encode.” */ + private Action useEarlyEncodeAction; + + /** Action when a priority was selected. */ + private Action priorityAction; + + /** The text field containing the directory. */ + private JTextField tempDirectoryTextField; + + /** The temp directory. */ + private String tempDirectory; + + /** The configuration location. */ + private ConfigurationLocation configurationLocation; + + /** Whether to use “early encode.” */ + private boolean useEarlyEncode; + + /** The prioriy for inserts. */ + private PriorityClass priority; + + /** The “default” button. */ + private JRadioButton defaultTempDirectory; + + /** The “custom” button. */ + private JRadioButton customTempDirectory; + + /** The “next to JAR file” checkbox. */ + private JRadioButton nextToJarFile; + + /** The “home directory” checkbox. */ + private JRadioButton homeDirectory; + + /** The “custom directory” checkbox. */ + private JRadioButton customDirectory; + + /** The “use early encode” checkbox. */ + private JCheckBox useEarlyEncodeCheckBox; + + /** The insert priority select box. */ + private JComboBox insertPriorityComboBox; + + /** The manifest putter select box. */ + private JComboBox manifestPutterComboBox; + + /** + * Creates a new “preferences” page. + * + * @param wizard + * The wizard this page belongs to + */ + public PreferencesPage(TWizard wizard) { + super(wizard); + pageInit(); + setHeading(I18n.getMessage("jsite.preferences.heading")); + setDescription(I18n.getMessage("jsite.preferences.description")); + I18nContainer.getInstance().registerRunnable(new Runnable() { + + /** + * {@inheritDoc} + */ ++ @Override + public void run() { + setHeading(I18n.getMessage("jsite.preferences.heading")); + setDescription(I18n.getMessage("jsite.preferences.description")); + } + }); + } + + // + // ACCESSORS + // + + /** + * Returns the temp directory. + * + * @return The temp directory, or {@code null} to use the default temp + * directory + */ + public String getTempDirectory() { + return tempDirectory; + } + + /** + * Sets the temp directory. + * + * @param tempDirectory + * The temp directory, or {@code null} to use the default temp + * directory + */ + public void setTempDirectory(String tempDirectory) { + this.tempDirectory = tempDirectory; + tempDirectoryTextField.setText((tempDirectory != null) ? tempDirectory : ""); + if (tempDirectory != null) { + customTempDirectory.setSelected(true); + chooseTempDirectoryAction.setEnabled(true); + } else { + defaultTempDirectory.setSelected(true); + } + } + + /** + * Returns the configuration location. + * + * @return The configuration location + */ + public ConfigurationLocation getConfigurationLocation() { + return configurationLocation; + } + + /** + * Sets the configuration location. + * + * @param configurationLocation + * The configuration location + */ + public void setConfigurationLocation(ConfigurationLocation configurationLocation) { + this.configurationLocation = configurationLocation; + switch (configurationLocation) { + case NEXT_TO_JAR_FILE: + nextToJarFile.setSelected(true); + break; + case HOME_DIRECTORY: + homeDirectory.setSelected(true); + break; + case CUSTOM: + customDirectory.setSelected(true); + break; + } + } + + /** + * Sets whether it is possible to select the “next to JAR file” option for + * the configuration location. + * + * @param nextToJarFile + * {@code true} if the configuration file can be saved next to + * the JAR file, {@code false} otherwise + */ + public void setHasNextToJarConfiguration(boolean nextToJarFile) { + this.nextToJarFile.setEnabled(nextToJarFile); + } + + /** + * Sets whether it is possible to select the “custom location” option for + * the configuration location. + * + * @param customDirectory + * {@code true} if the configuration file can be saved to a + * custom location, {@code false} otherwise + */ + public void setHasCustomConfiguration(boolean customDirectory) { + this.customDirectory.setEnabled(customDirectory); + } + + /** + * Returns whether to use the “early encode“ flag for the insert. + * + * @return {@code true} to set the “early encode” flag for the insert, + * {@code false} otherwise + */ + public boolean useEarlyEncode() { + return useEarlyEncode; + } + + /** + * Sets whether to use the “early encode“ flag for the insert. + * + * @param useEarlyEncode + * {@code true} to set the “early encode” flag for the insert, + * {@code false} otherwise + */ + public void setUseEarlyEncode(boolean useEarlyEncode) { + useEarlyEncodeCheckBox.setSelected(useEarlyEncode); + } + + /** + * Returns the configured insert priority. + * + * @return The insert priority + */ + public PriorityClass getPriority() { + return priority; + } + + /** + * Sets the insert priority. + * + * @param priority + * The insert priority + */ + public void setPriority(PriorityClass priority) { + insertPriorityComboBox.setSelectedItem(priority); + } + + /** + * Returns the selected manifest putter. + * + * @return The selected manifest putter + */ + public ManifestPutter getManifestPutter() { + return (ManifestPutter) manifestPutterComboBox.getSelectedItem(); + } + + /** + * Sets the manifest putter. + * + * @param manifestPutter + * The manifest putter + */ + public void setManifestPutter(ManifestPutter manifestPutter) { + manifestPutterComboBox.setSelectedItem(manifestPutter); + } + + /** + * {@inheritDoc} + */ + @Override + public void pageAdded(TWizard wizard) { + super.pageAdded(wizard); + this.wizard.setPreviousName(I18n.getMessage("jsite.menu.nodes.manage-nodes")); + this.wizard.setNextName(I18n.getMessage("jsite.wizard.next")); + this.wizard.setQuitName(I18n.getMessage("jsite.wizard.quit")); + this.wizard.setNextEnabled(false); + } + + // + // PRIVATE METHODS + // + + /** + * Initializes this page. + */ + private void pageInit() { + createActions(); + setLayout(new BorderLayout(12, 12)); + add(createPreferencesPanel(), BorderLayout.CENTER); + } + + /** + * Creates all actions. + */ + private void createActions() { + selectDefaultTempDirectoryAction = new AbstractAction(I18n.getMessage("jsite.preferences.temp-directory.default")) { + + /** + * {@inheritDoc} + */ ++ @Override + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + selectDefaultTempDirectory(); + } + }; + selectCustomTempDirectoryAction = new AbstractAction(I18n.getMessage("jsite.preferences.temp-directory.custom")) { + + /** + * {@inheritDoc} + */ ++ @Override + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + selectCustomTempDirectory(); + } + }; + chooseTempDirectoryAction = new AbstractAction(I18n.getMessage("jsite.preferences.temp-directory.choose")) { + ++ @Override + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent e) { + chooseTempDirectory(); + } + }; + nextToJarFileAction = new AbstractAction(I18n.getMessage("jsite.preferences.config-directory.jar")) { + ++ @Override + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionevent) { + configurationLocation = ConfigurationLocation.NEXT_TO_JAR_FILE; + } + }; + homeDirectoryAction = new AbstractAction(I18n.getMessage("jsite.preferences.config-directory.home")) { + ++ @Override + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionevent) { + configurationLocation = ConfigurationLocation.HOME_DIRECTORY; + } + }; + customDirectoryAction = new AbstractAction(I18n.getMessage("jsite.preferences.config-directory.custom")) { + ++ @Override + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + configurationLocation = ConfigurationLocation.CUSTOM; + } + }; + useEarlyEncodeAction = new AbstractAction(I18n.getMessage("jsite.preferences.insert-options.use-early-encode")) { + ++ @Override + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + useEarlyEncode = useEarlyEncodeCheckBox.isSelected(); + } + }; + priorityAction = new AbstractAction(I18n.getMessage("jsite.preferences.insert-options.priority")) { + ++ @Override + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + priority = (PriorityClass) insertPriorityComboBox.getSelectedItem(); + } + }; + + I18nContainer.getInstance().registerRunnable(new Runnable() { + ++ @Override + @SuppressWarnings("synthetic-access") + public void run() { + selectDefaultTempDirectoryAction.putValue(Action.NAME, I18n.getMessage("jsite.preferences.temp-directory.default")); + selectCustomTempDirectoryAction.putValue(Action.NAME, I18n.getMessage("jsite.preferences.temp-directory.custom")); + chooseTempDirectoryAction.putValue(Action.NAME, I18n.getMessage("jsite.preferences.temp-directory.choose")); + nextToJarFileAction.putValue(Action.NAME, I18n.getMessage("jsite.preferences.config-directory.jar")); + homeDirectoryAction.putValue(Action.NAME, I18n.getMessage("jsite.preferences.config-directory.home")); + customDirectoryAction.putValue(Action.NAME, I18n.getMessage("jsite.preferences.config-directory.custom")); + useEarlyEncodeAction.putValue(Action.NAME, I18n.getMessage("jsite.preferences.insert-options.use-early-encode")); + } + }); + } + + /** + * Creates the panel containing all preferences. + * + * @return The preferences panel + */ + private JPanel createPreferencesPanel() { + JPanel preferencesPanel = new JPanel(new GridBagLayout()); + preferencesPanel.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12)); + + final JLabel tempDirectoryLabel = new JLabel("" + I18n.getMessage("jsite.preferences.temp-directory") + ""); + preferencesPanel.add(tempDirectoryLabel, new GridBagConstraints(0, 0, 3, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); + + defaultTempDirectory = new JRadioButton(selectDefaultTempDirectoryAction); + preferencesPanel.add(defaultTempDirectory, new GridBagConstraints(0, 1, 3, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(6, 18, 0, 0), 0, 0)); + + customTempDirectory = new JRadioButton(selectCustomTempDirectoryAction); + preferencesPanel.add(customTempDirectory, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 18, 0, 0), 0, 0)); + + ButtonGroup tempDirectoryButtonGroup = new ButtonGroup(); + defaultTempDirectory.getModel().setGroup(tempDirectoryButtonGroup); + customTempDirectory.getModel().setGroup(tempDirectoryButtonGroup); + + tempDirectoryTextField = new JTextField(); + tempDirectoryTextField.setEditable(false); + if (tempDirectory != null) { + tempDirectoryTextField.setText(tempDirectory); + customTempDirectory.setSelected(true); + } else { + defaultTempDirectory.setSelected(true); + } + chooseTempDirectoryAction.setEnabled(tempDirectory != null); + preferencesPanel.add(tempDirectoryTextField, new GridBagConstraints(1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 6, 0, 0), 0, 0)); + + JButton chooseButton = new JButton(chooseTempDirectoryAction); + preferencesPanel.add(chooseButton, new GridBagConstraints(2, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.BOTH, new Insets(0, 6, 0, 0), 0, 0)); + + final JLabel configurationDirectoryLabel = new JLabel("" + I18n.getMessage("jsite.preferences.config-directory") + ""); + preferencesPanel.add(configurationDirectoryLabel, new GridBagConstraints(0, 3, 3, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(12, 0, 0, 0), 0, 0)); + + nextToJarFile = new JRadioButton(nextToJarFileAction); + preferencesPanel.add(nextToJarFile, new GridBagConstraints(0, 4, 3, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(6, 18, 0, 0), 0, 0)); + + homeDirectory = new JRadioButton(homeDirectoryAction); + preferencesPanel.add(homeDirectory, new GridBagConstraints(0, 5, 3, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 18, 0, 0), 0, 0)); + + customDirectory = new JRadioButton(customDirectoryAction); + preferencesPanel.add(customDirectory, new GridBagConstraints(0, 6, 3, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 18, 0, 0), 0, 0)); + + ButtonGroup configurationDirectoryButtonGroup = new ButtonGroup(); + configurationDirectoryButtonGroup.add(nextToJarFile); + configurationDirectoryButtonGroup.add(homeDirectory); + configurationDirectoryButtonGroup.add(customDirectory); + + final JLabel insertOptionsLabel = new JLabel("" + I18n.getMessage("jsite.preferences.insert-options") + ""); + preferencesPanel.add(insertOptionsLabel, new GridBagConstraints(0, 7, 3, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(12, 0, 0, 0), 0, 0)); + + useEarlyEncodeCheckBox = new JCheckBox(useEarlyEncodeAction); + preferencesPanel.add(useEarlyEncodeCheckBox, new GridBagConstraints(0, 8, 3, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 18, 0, 0), 0, 0)); + + final JLabel insertPriorityLabel = new JLabel(I18n.getMessage("jsite.preferences.insert-options.priority")); + preferencesPanel.add(insertPriorityLabel, new GridBagConstraints(0, 9, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 18, 0, 0), 0, 0)); + + insertPriorityComboBox = new JComboBox(new PriorityClass[] { PriorityClass.MINIMUM, PriorityClass.PREFETCH, PriorityClass.BULK, PriorityClass.UPDATABLE, PriorityClass.SEMI_INTERACTIVE, PriorityClass.INTERACTIVE, PriorityClass.MAXIMUM }); + insertPriorityComboBox.setAction(priorityAction); + preferencesPanel.add(insertPriorityComboBox, new GridBagConstraints(1, 9, 2, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 18, 0, 0), 0, 0)); + + final JLabel manifestPutterLabel = new JLabel(I18n.getMessage("jsite.preferences.insert-options.manifest-putter")); + preferencesPanel.add(manifestPutterLabel, new GridBagConstraints(0, 10, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 18, 0, 0), 0, 0)); + + manifestPutterComboBox = new JComboBox(ManifestPutter.values()); + preferencesPanel.add(manifestPutterComboBox, new GridBagConstraints(1, 10, 2, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 18, 0, 0), 0, 0)); + + I18nContainer.getInstance().registerRunnable(new Runnable() { + + /** + * {@inheritDoc} + */ ++ @Override + public void run() { + tempDirectoryLabel.setText("" + I18n.getMessage("jsite.preferences.temp-directory") + ""); + configurationDirectoryLabel.setText("" + I18n.getMessage("jsite.preferences.config-directory") + ""); + insertOptionsLabel.setText("" + I18n.getMessage("jsite.preferences.insert-options") + ""); + insertPriorityLabel.setText(I18n.getMessage("jsite.preferences.insert-options.priority")); + manifestPutterLabel.setText(I18n.getMessage("jsite.preferences.insert-options.manifest-putter")); + } + }); + + return preferencesPanel; + } + + /** + * Activates the default temp directory radio button. + */ + private void selectDefaultTempDirectory() { + tempDirectoryTextField.setEnabled(false); + chooseTempDirectoryAction.setEnabled(false); + tempDirectory = null; + } + + /** + * Activates the custom temp directory radio button. + */ + private void selectCustomTempDirectory() { + tempDirectoryTextField.setEnabled(true); + chooseTempDirectoryAction.setEnabled(true); + if (tempDirectoryTextField.getText().length() == 0) { + chooseTempDirectory(); + if (tempDirectoryTextField.getText().length() == 0) { + defaultTempDirectory.setSelected(true); + } + } + } + + /** + * Lets the user choose a new temp directory. + */ + private void chooseTempDirectory() { + JFileChooser fileChooser = new JFileChooser(tempDirectory); + fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + int returnValue = fileChooser.showDialog(wizard, I18n.getMessage("jsite.preferences.temp-directory.choose.approve")); + if (returnValue == JFileChooser.CANCEL_OPTION) { + return; + } + tempDirectory = fileChooser.getSelectedFile().getPath(); + tempDirectoryTextField.setText(tempDirectory); + } + + } diff --cc src/main/java/de/todesbaum/jsite/gui/ProjectFilesPage.java index 0000000,014a497..5b26661 mode 000000,100644..100644 --- a/src/main/java/de/todesbaum/jsite/gui/ProjectFilesPage.java +++ b/src/main/java/de/todesbaum/jsite/gui/ProjectFilesPage.java @@@ -1,0 -1,577 +1,592 @@@ + /* + * jSite - ProjectFilesPage.java - Copyright © 2006–2012 David Roden + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + package de.todesbaum.jsite.gui; + + import java.awt.BorderLayout; + import java.awt.Dimension; + import java.awt.GridBagConstraints; + import java.awt.GridBagLayout; + import java.awt.Insets; + import java.awt.event.ActionEvent; + import java.awt.event.ActionListener; + import java.awt.event.KeyEvent; + import java.text.MessageFormat; + import java.util.HashSet; + import java.util.Iterator; + import java.util.List; + import java.util.Set; + + import javax.swing.AbstractAction; + import javax.swing.Action; + import javax.swing.JButton; + import javax.swing.JCheckBox; + import javax.swing.JComboBox; + import javax.swing.JComponent; + import javax.swing.JLabel; + import javax.swing.JList; + import javax.swing.JOptionPane; + import javax.swing.JPanel; + import javax.swing.JScrollPane; + import javax.swing.JTextField; + import javax.swing.ListSelectionModel; + import javax.swing.SwingUtilities; + import javax.swing.event.DocumentEvent; + import javax.swing.event.DocumentListener; + import javax.swing.event.ListSelectionEvent; + import javax.swing.event.ListSelectionListener; + import javax.swing.text.BadLocationException; + import javax.swing.text.Document; + + import net.pterodactylus.util.io.MimeTypes; + import de.todesbaum.jsite.application.FileOption; + import de.todesbaum.jsite.application.Project; + import de.todesbaum.jsite.gui.FileScanner.ScannedFile; + import de.todesbaum.jsite.i18n.I18n; + import de.todesbaum.jsite.i18n.I18nContainer; + import de.todesbaum.util.swing.TLabel; + import de.todesbaum.util.swing.TWizard; + import de.todesbaum.util.swing.TWizardPage; + + /** + * Wizard page that lets the user manage the files of a project. + * + * @author David ‘Bombe’ Roden <bombe@freenetproject.org> + */ + public class ProjectFilesPage extends TWizardPage implements ActionListener, ListSelectionListener, DocumentListener, FileScannerListener { + + /** The project. */ + private Project project; + + /** The “scan files” action. */ + private Action scanAction; + + /** The “ignore hidden files” checkbox. */ + private JCheckBox ignoreHiddenFilesCheckBox; + + /** The list of project files. */ + private JList projectFileList; + + /** The “default file” checkbox. */ + private JCheckBox defaultFileCheckBox; + + /** The “insert” checkbox. */ + private JCheckBox fileOptionsInsertCheckBox; + + /** The “force insert” checkbox. */ + private JCheckBox fileOptionsForceInsertCheckBox; + + /** The “insert redirect” checkbox. */ + private JCheckBox fileOptionsInsertRedirectCheckBox; + + /** The “custom key” textfield. */ + private JTextField fileOptionsCustomKeyTextField; + + /** The “rename” check box. */ + private JCheckBox fileOptionsRenameCheckBox; + + /** The “new name” text field. */ + private JTextField fileOptionsRenameTextField; + + /** The “mime type” combo box. */ + private JComboBox fileOptionsMIMETypeComboBox; + + /** + * Creates a new project file page. + * + * @param wizard + * The wizard the page belongs to + */ + public ProjectFilesPage(final TWizard wizard) { + super(wizard); + pageInit(); + } + + /** + * Initializes the page and all its actions and components. + */ + private void pageInit() { + createActions(); + setLayout(new BorderLayout(12, 12)); + add(createProjectFilesPanel(), BorderLayout.CENTER); + } + + /** + * Creates all actions. + */ + private void createActions() { + scanAction = new AbstractAction(I18n.getMessage("jsite.project-files.action.rescan")) { + ++ @Override + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + actionScan(); + } + }; + scanAction.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_S); + scanAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project-files.action.rescan.tooltip")); + + I18nContainer.getInstance().registerRunnable(new Runnable() { + ++ @Override + @SuppressWarnings("synthetic-access") + public void run() { + scanAction.putValue(Action.NAME, I18n.getMessage("jsite.project-files.action.rescan")); + scanAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project-files.action.rescan.tooltip")); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public void pageAdded(TWizard wizard) { + actionScan(); + this.wizard.setPreviousName(I18n.getMessage("jsite.wizard.previous")); + this.wizard.setNextName(I18n.getMessage("jsite.project-files.insert-now")); + this.wizard.setQuitName(I18n.getMessage("jsite.wizard.quit")); + } + + /** + * Creates the panel contains the project file list and options. + * + * @return The created panel + */ + private JComponent createProjectFilesPanel() { + JPanel projectFilesPanel = new JPanel(new BorderLayout(12, 12)); + + projectFileList = new JList(); + projectFileList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + projectFileList.setMinimumSize(new Dimension(250, projectFileList.getPreferredSize().height)); + projectFileList.addListSelectionListener(this); + + projectFilesPanel.add(new JScrollPane(projectFileList), BorderLayout.CENTER); + + JPanel fileOptionsAlignmentPanel = new JPanel(new BorderLayout(12, 12)); + projectFilesPanel.add(fileOptionsAlignmentPanel, BorderLayout.PAGE_END); + JPanel fileOptionsPanel = new JPanel(new GridBagLayout()); + fileOptionsAlignmentPanel.add(fileOptionsPanel, BorderLayout.PAGE_START); + + ignoreHiddenFilesCheckBox = new JCheckBox(I18n.getMessage("jsite.project-files.ignore-hidden-files")); + ignoreHiddenFilesCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.ignore-hidden-files.tooltip")); + ignoreHiddenFilesCheckBox.setName("ignore-hidden-files"); + ignoreHiddenFilesCheckBox.addActionListener(this); + fileOptionsPanel.add(ignoreHiddenFilesCheckBox, new GridBagConstraints(0, 0, 5, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); + + fileOptionsPanel.add(new JButton(scanAction), new GridBagConstraints(0, 1, 5, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 0, 0, 0), 0, 0)); + + final JLabel fileOptionsLabel = new JLabel("" + I18n.getMessage("jsite.project-files.file-options") + ""); + fileOptionsPanel.add(fileOptionsLabel, new GridBagConstraints(0, 2, 5, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 0, 0, 0), 0, 0)); + + defaultFileCheckBox = new JCheckBox(I18n.getMessage("jsite.project-files.default")); + defaultFileCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.default.tooltip")); + defaultFileCheckBox.setName("default-file"); + defaultFileCheckBox.addActionListener(this); + defaultFileCheckBox.setEnabled(false); + + fileOptionsPanel.add(defaultFileCheckBox, new GridBagConstraints(0, 3, 5, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 18, 0, 0), 0, 0)); + + fileOptionsInsertCheckBox = new JCheckBox(I18n.getMessage("jsite.project-files.insert"), true); + fileOptionsInsertCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.insert.tooltip")); + fileOptionsInsertCheckBox.setName("insert"); + fileOptionsInsertCheckBox.setMnemonic(KeyEvent.VK_I); + fileOptionsInsertCheckBox.addActionListener(this); + fileOptionsInsertCheckBox.setEnabled(false); + + fileOptionsPanel.add(fileOptionsInsertCheckBox, new GridBagConstraints(0, 4, 5, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 18, 0, 0), 0, 0)); + + fileOptionsForceInsertCheckBox = new JCheckBox(I18n.getMessage("jsite.project-files.force-insert")); + fileOptionsForceInsertCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.force-insert.tooltip")); + fileOptionsForceInsertCheckBox.setName("force-insert"); + fileOptionsForceInsertCheckBox.setMnemonic(KeyEvent.VK_F); + fileOptionsForceInsertCheckBox.addActionListener(this); + fileOptionsForceInsertCheckBox.setEnabled(false); + + fileOptionsPanel.add(fileOptionsForceInsertCheckBox, new GridBagConstraints(0, 5, 5, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 18, 0, 0), 0, 0)); + + fileOptionsCustomKeyTextField = new JTextField(45); + fileOptionsCustomKeyTextField.setToolTipText(I18n.getMessage("jsite.project-files.custom-key.tooltip")); + fileOptionsCustomKeyTextField.setEnabled(false); + fileOptionsCustomKeyTextField.getDocument().addDocumentListener(this); + + fileOptionsInsertRedirectCheckBox = new JCheckBox(I18n.getMessage("jsite.project-files.insert-redirect"), false); + fileOptionsInsertRedirectCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.insert-redirect.tooltip")); + fileOptionsInsertRedirectCheckBox.setName("insert-redirect"); + fileOptionsInsertRedirectCheckBox.setMnemonic(KeyEvent.VK_R); + fileOptionsInsertRedirectCheckBox.addActionListener(this); + fileOptionsInsertRedirectCheckBox.setEnabled(false); + + final TLabel customKeyLabel = new TLabel(I18n.getMessage("jsite.project-files.custom-key") + ":", KeyEvent.VK_K, fileOptionsCustomKeyTextField); + fileOptionsPanel.add(fileOptionsInsertRedirectCheckBox, new GridBagConstraints(0, 6, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 18, 0, 0), 0, 0)); + fileOptionsPanel.add(customKeyLabel, new GridBagConstraints(1, 6, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 6, 0, 0), 0, 0)); + fileOptionsPanel.add(fileOptionsCustomKeyTextField, new GridBagConstraints(2, 6, 3, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0)); + + fileOptionsRenameCheckBox = new JCheckBox(I18n.getMessage("jsite.project-files.rename"), false); + fileOptionsRenameCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.rename.tooltip")); + fileOptionsRenameCheckBox.setName("rename"); + fileOptionsRenameCheckBox.setMnemonic(KeyEvent.VK_N); + fileOptionsRenameCheckBox.addActionListener(this); + fileOptionsRenameCheckBox.setEnabled(false); + + fileOptionsRenameTextField = new JTextField(); + fileOptionsRenameTextField.setEnabled(false); + fileOptionsRenameTextField.getDocument().addDocumentListener(new DocumentListener() { + + @SuppressWarnings("synthetic-access") + private void storeText(DocumentEvent documentEvent) { + FileOption fileOption = getSelectedFile(); + if (fileOption == null) { + /* no file selected. */ + return; + } + Document document = documentEvent.getDocument(); + int documentLength = document.getLength(); + try { + fileOption.setChangedName(document.getText(0, documentLength).trim()); + } catch (BadLocationException ble1) { + /* ignore, it should never happen. */ + } + } + ++ @Override + public void changedUpdate(DocumentEvent documentEvent) { + storeText(documentEvent); + } + ++ @Override + public void insertUpdate(DocumentEvent documentEvent) { + storeText(documentEvent); + } + ++ @Override + public void removeUpdate(DocumentEvent documentEvent) { + storeText(documentEvent); + } + + }); + + fileOptionsPanel.add(fileOptionsRenameCheckBox, new GridBagConstraints(0, 7, 2, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 18, 0, 0), 0, 0)); + fileOptionsPanel.add(fileOptionsRenameTextField, new GridBagConstraints(2, 7, 3, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0)); + + fileOptionsMIMETypeComboBox = new JComboBox(MimeTypes.getAllMimeTypes().toArray()); + fileOptionsMIMETypeComboBox.setToolTipText(I18n.getMessage("jsite.project-files.mime-type.tooltip")); + fileOptionsMIMETypeComboBox.setName("project-files.mime-type"); + fileOptionsMIMETypeComboBox.addActionListener(this); + fileOptionsMIMETypeComboBox.setEditable(true); + fileOptionsMIMETypeComboBox.setEnabled(false); + + final TLabel mimeTypeLabel = new TLabel(I18n.getMessage("jsite.project-files.mime-type") + ":", KeyEvent.VK_M, fileOptionsMIMETypeComboBox); + fileOptionsPanel.add(mimeTypeLabel, new GridBagConstraints(0, 8, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 18, 0, 0), 0, 0)); + fileOptionsPanel.add(fileOptionsMIMETypeComboBox, new GridBagConstraints(1, 8, 4, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0)); + + I18nContainer.getInstance().registerRunnable(new Runnable() { + ++ @Override + @SuppressWarnings("synthetic-access") + public void run() { + ignoreHiddenFilesCheckBox.setText(I18n.getMessage("jsite.project-files.ignore-hidden-files")); + ignoreHiddenFilesCheckBox.setToolTipText(I18n.getMessage("jsite.projet-files.ignore-hidden-files.tooltip")); + fileOptionsLabel.setText("" + I18n.getMessage("jsite.project-files.file-options") + ""); + defaultFileCheckBox.setText(I18n.getMessage("jsite.project-files.default")); + defaultFileCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.default.tooltip")); + fileOptionsInsertCheckBox.setText(I18n.getMessage("jsite.project-files.insert")); + fileOptionsInsertCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.insert.tooltip")); + fileOptionsForceInsertCheckBox.setText(I18n.getMessage("jsite.project-files.force-insert")); + fileOptionsForceInsertCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.force-insert.tooltip")); + fileOptionsInsertRedirectCheckBox.setText(I18n.getMessage("jsite.project-files.insert-redirect")); + fileOptionsInsertRedirectCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.insert-redirect.tooltip")); + fileOptionsCustomKeyTextField.setToolTipText(I18n.getMessage("jsite.project-files.custom-key.tooltip")); + customKeyLabel.setText(I18n.getMessage("jsite.project-files.custom-key") + ":"); + fileOptionsRenameCheckBox.setText("jsite.project-files.rename"); + fileOptionsRenameCheckBox.setToolTipText("jsite.project-files.rename.tooltip"); + fileOptionsMIMETypeComboBox.setToolTipText(I18n.getMessage("jsite.project-files.mime-type.tooltip")); + mimeTypeLabel.setText(I18n.getMessage("jsite.project-files.mime-type") + ":"); + } + }); + + return projectFilesPanel; + } + + /** + * Sets the project whose files to manage. + * + * @param project + * The project whose files to manage + */ + public void setProject(final Project project) { + this.project = project; + setHeading(MessageFormat.format(I18n.getMessage("jsite.project-files.heading"), project.getName())); + setDescription(I18n.getMessage("jsite.project-files.description")); + ignoreHiddenFilesCheckBox.setSelected(project.isIgnoreHiddenFiles()); + I18nContainer.getInstance().registerRunnable(new Runnable() { + ++ @Override + public void run() { + setHeading(MessageFormat.format(I18n.getMessage("jsite.project-files.heading"), project.getName())); + setDescription(I18n.getMessage("jsite.project-files.description")); + } + }); + } + + // + // ACTIONS + // + + /** + * Rescans the project’s files. + */ + private void actionScan() { + projectFileList.clearSelection(); + projectFileList.setListData(new Object[0]); + + wizard.setNextEnabled(false); + wizard.setPreviousEnabled(false); + wizard.setQuitEnabled(false); + + FileScanner fileScanner = new FileScanner(project); + fileScanner.addFileScannerListener(this); + new Thread(fileScanner).start(); + } + + /** + * {@inheritDoc} + *

+ * Updates the file list. + */ ++ @Override + public void fileScannerFinished(FileScanner fileScanner) { + final boolean error = fileScanner.isError(); + if (!error) { + final List files = fileScanner.getFiles(); + SwingUtilities.invokeLater(new Runnable() { + ++ @Override + @SuppressWarnings("synthetic-access") + public void run() { + projectFileList.setListData(files.toArray()); + projectFileList.clearSelection(); + } + }); + Set entriesToRemove = new HashSet(); + Iterator filenames = new HashSet(project.getFileOptions().keySet()).iterator(); + while (filenames.hasNext()) { + String filename = filenames.next(); + boolean found = false; + for (ScannedFile scannedFile : files) { + if (scannedFile.getFilename().equals(filename)) { + found = true; + break; + } + } + if (!found) { + entriesToRemove.add(filename); + } + } + for (String filename : entriesToRemove) { + project.setFileOption(filename, null); + } + } else { + JOptionPane.showMessageDialog(wizard, I18n.getMessage("jsite.project-files.scan-error"), null, JOptionPane.ERROR_MESSAGE); + } + SwingUtilities.invokeLater(new Runnable() { + ++ @Override + @SuppressWarnings("synthetic-access") + public void run() { + wizard.setPreviousEnabled(true); + wizard.setNextEnabled(!error); + wizard.setQuitEnabled(true); + } + }); + } + + /** + * Returns the {@link FileOption file options} for the currently selected + * file. + * + * @return The {@link FileOption}s for the selected file, or {@code null} if + * no file is selected + */ + private FileOption getSelectedFile() { + ScannedFile scannedFile = (ScannedFile) projectFileList.getSelectedValue(); + if (scannedFile == null) { + return null; + } + return project.getFileOption(scannedFile.getFilename()); + } + + // + // INTERFACE ActionListener + // + + /** + * {@inheritDoc} + */ ++ @Override + public void actionPerformed(ActionEvent actionEvent) { + Object source = actionEvent.getSource(); + if ((source instanceof JCheckBox) && ("ignore-hidden-files".equals(((JCheckBox) source).getName()))) { + project.setIgnoreHiddenFiles(((JCheckBox) source).isSelected()); + actionScan(); + return; + } + ScannedFile scannedFile = (ScannedFile) projectFileList.getSelectedValue(); + if (scannedFile == null) { + return; + } + String filename = scannedFile.getFilename(); + FileOption fileOption = project.getFileOption(filename); + if (source instanceof JCheckBox) { + JCheckBox checkBox = (JCheckBox) source; + if ("default-file".equals(checkBox.getName())) { + if (checkBox.isSelected()) { + if (filename.indexOf('/') > -1) { + JOptionPane.showMessageDialog(wizard, I18n.getMessage("jsite.project-files.invalid-default-file"), null, JOptionPane.ERROR_MESSAGE); + checkBox.setSelected(false); + } else { + project.setIndexFile(filename); + } + } else { + if (filename.equals(project.getIndexFile())) { + project.setIndexFile(null); + } + } + } else if ("insert".equals(checkBox.getName())) { + boolean isInsert = checkBox.isSelected(); + fileOption.setInsert(isInsert); + fileOptionsInsertRedirectCheckBox.setEnabled(!isInsert); + } else if ("force-insert".equals(checkBox.getName())) { + boolean isForceInsert = checkBox.isSelected(); + fileOption.setForceInsert(isForceInsert); + } else if ("insert-redirect".equals(checkBox.getName())) { + boolean isInsertRedirect = checkBox.isSelected(); + fileOption.setInsertRedirect(isInsertRedirect); + fileOptionsCustomKeyTextField.setEnabled(isInsertRedirect); + } else if ("rename".equals(checkBox.getName())) { + boolean isRenamed = checkBox.isSelected(); + fileOptionsRenameTextField.setEnabled(isRenamed); + fileOption.setChangedName(isRenamed ? fileOptionsRenameTextField.getText() : ""); + } + } else if (source instanceof JComboBox) { + JComboBox comboBox = (JComboBox) source; + if ("project-files.mime-type".equals(comboBox.getName())) { + fileOption.setMimeType((String) comboBox.getSelectedItem()); + } + } + } + + // + // INTERFACE ListSelectionListener + // + + /** + * {@inheritDoc} + */ ++ @Override + @SuppressWarnings("null") + public void valueChanged(ListSelectionEvent e) { + ScannedFile scannedFile = (ScannedFile) projectFileList.getSelectedValue(); + boolean enabled = scannedFile != null; + String filename = (scannedFile == null) ? null : scannedFile.getFilename(); + defaultFileCheckBox.setEnabled(enabled); + fileOptionsInsertCheckBox.setEnabled(enabled); + fileOptionsRenameCheckBox.setEnabled(enabled); + fileOptionsMIMETypeComboBox.setEnabled(enabled); + if (filename != null) { + FileOption fileOption = project.getFileOption(filename); + defaultFileCheckBox.setSelected(filename.equals(project.getIndexFile())); + fileOptionsInsertCheckBox.setSelected(fileOption.isInsert()); + fileOptionsForceInsertCheckBox.setEnabled(scannedFile.getHash().equals(fileOption.getLastInsertHash())); + fileOptionsForceInsertCheckBox.setSelected(fileOption.isForceInsert()); + fileOptionsInsertRedirectCheckBox.setEnabled(!fileOption.isInsert()); + fileOptionsInsertRedirectCheckBox.setSelected(fileOption.isInsertRedirect()); + fileOptionsCustomKeyTextField.setEnabled(fileOption.isInsertRedirect()); + fileOptionsCustomKeyTextField.setText(fileOption.getCustomKey()); + fileOptionsRenameCheckBox.setSelected(fileOption.hasChangedName()); + fileOptionsRenameTextField.setEnabled(fileOption.hasChangedName()); + fileOptionsRenameTextField.setText(fileOption.getChangedName()); + fileOptionsMIMETypeComboBox.getModel().setSelectedItem(fileOption.getMimeType()); + } else { + defaultFileCheckBox.setSelected(false); + fileOptionsInsertCheckBox.setSelected(true); + fileOptionsForceInsertCheckBox.setEnabled(false); + fileOptionsForceInsertCheckBox.setSelected(false); + fileOptionsInsertRedirectCheckBox.setEnabled(false); + fileOptionsInsertRedirectCheckBox.setSelected(false); + fileOptionsCustomKeyTextField.setEnabled(false); + fileOptionsCustomKeyTextField.setText("CHK@"); + fileOptionsRenameCheckBox.setEnabled(false); + fileOptionsRenameCheckBox.setSelected(false); + fileOptionsRenameTextField.setEnabled(false); + fileOptionsRenameTextField.setText(""); + fileOptionsMIMETypeComboBox.getModel().setSelectedItem(MimeTypes.DEFAULT_CONTENT_TYPE); + } + } + + // + // INTERFACE DocumentListener + // + + /** + * Updates the options of the currently selected file with the changes made + * in the “custom key” textfield. + * + * @param documentEvent + * The document event to process + */ + private void processDocumentUpdate(DocumentEvent documentEvent) { + ScannedFile scannedFile = (ScannedFile) projectFileList.getSelectedValue(); + if (scannedFile == null) { + return; + } + FileOption fileOption = project.getFileOption(scannedFile.getFilename()); + Document document = documentEvent.getDocument(); + try { + String text = document.getText(0, document.getLength()); + fileOption.setCustomKey(text); + } catch (BadLocationException ble1) { + /* ignore. */ + } + } + + /** + * {@inheritDoc} + */ ++ @Override + public void changedUpdate(DocumentEvent documentEvent) { + processDocumentUpdate(documentEvent); + } + + /** + * {@inheritDoc} + */ ++ @Override + public void insertUpdate(DocumentEvent documentEvent) { + processDocumentUpdate(documentEvent); + } + + /** + * {@inheritDoc} + */ ++ @Override + public void removeUpdate(DocumentEvent documentEvent) { + processDocumentUpdate(documentEvent); + } + + } diff --cc src/main/java/de/todesbaum/jsite/gui/ProjectInsertPage.java index 0000000,2a3e5e5..b73a49a mode 000000,100644..100644 --- a/src/main/java/de/todesbaum/jsite/gui/ProjectInsertPage.java +++ b/src/main/java/de/todesbaum/jsite/gui/ProjectInsertPage.java @@@ -1,0 -1,534 +1,552 @@@ + /* + * jSite - ProjectInsertPage.java - Copyright © 2006–2012 David Roden + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + package de.todesbaum.jsite.gui; + + import java.awt.BorderLayout; + import java.awt.Font; + import java.awt.GridBagConstraints; + import java.awt.GridBagLayout; + import java.awt.Insets; + import java.awt.Toolkit; + import java.awt.datatransfer.Clipboard; + import java.awt.datatransfer.ClipboardOwner; + import java.awt.datatransfer.StringSelection; + import java.awt.datatransfer.Transferable; + import java.awt.event.ActionEvent; + import java.awt.event.KeyEvent; + import java.text.DateFormat; + import java.text.MessageFormat; + import java.util.Date; + import java.util.logging.Level; + import java.util.logging.Logger; + + import javax.swing.AbstractAction; + import javax.swing.Action; + import javax.swing.JButton; + import javax.swing.JComponent; + import javax.swing.JLabel; + import javax.swing.JOptionPane; + import javax.swing.JPanel; + import javax.swing.JProgressBar; + import javax.swing.JTextField; + import javax.swing.SwingUtilities; + + import net.pterodactylus.util.io.StreamCopier.ProgressListener; + import de.todesbaum.jsite.application.AbortedException; + import de.todesbaum.jsite.application.Freenet7Interface; + import de.todesbaum.jsite.application.InsertListener; + import de.todesbaum.jsite.application.Project; + import de.todesbaum.jsite.application.ProjectInserter; + import de.todesbaum.jsite.i18n.I18n; + import de.todesbaum.jsite.i18n.I18nContainer; + import de.todesbaum.util.freenet.fcp2.ClientPutDir.ManifestPutter; + import de.todesbaum.util.freenet.fcp2.PriorityClass; + import de.todesbaum.util.swing.TWizard; + import de.todesbaum.util.swing.TWizardPage; + + /** + * Wizard page that shows the progress of an insert. + * + * @author David ‘Bombe’ Roden <bombe@freenetproject.org> + */ + public class ProjectInsertPage extends TWizardPage implements InsertListener, ClipboardOwner { + + /** The logger. */ + private static final Logger logger = Logger.getLogger(ProjectInsertPage.class.getName()); + + /** The project inserter. */ + private ProjectInserter projectInserter; + + /** The “copy URI” action. */ + private Action copyURIAction; + + /** The “request URI” textfield. */ + private JTextField requestURITextField; + + /** The “start time” label. */ + private JLabel startTimeLabel; + + /** The progress bar. */ + private JProgressBar progressBar; + + /** The start time of the insert. */ + private long startTime = 0; + + /** The number of inserted blocks. */ + private volatile int insertedBlocks; + + /** Whether the “copy URI to clipboard” button was used. */ + private boolean uriCopied; + + /** Whether the insert is currently running. */ + private volatile boolean running = false; + + /** + * Creates a new progress insert wizard page. + * + * @param wizard + * The wizard this page belongs to + */ + public ProjectInsertPage(final TWizard wizard) { + super(wizard); + createActions(); + pageInit(); + setHeading(I18n.getMessage("jsite.insert.heading")); + setDescription(I18n.getMessage("jsite.insert.description")); + I18nContainer.getInstance().registerRunnable(new Runnable() { + ++ @Override + public void run() { + setHeading(I18n.getMessage("jsite.insert.heading")); + setDescription(I18n.getMessage("jsite.insert.description")); + } + }); + projectInserter = new ProjectInserter(); + projectInserter.addInsertListener(this); + } + + /** + * Creates all used actions. + */ + private void createActions() { + copyURIAction = new AbstractAction(I18n.getMessage("jsite.project.action.copy-uri")) { + ++ @Override + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + actionCopyURI(); + } + }; + copyURIAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project.action.copy-uri.tooltip")); + copyURIAction.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_U); + copyURIAction.setEnabled(false); + + I18nContainer.getInstance().registerRunnable(new Runnable() { + ++ @Override + @SuppressWarnings("synthetic-access") + public void run() { + copyURIAction.putValue(Action.NAME, I18n.getMessage("jsite.project.action.copy-uri")); + copyURIAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project.action.copy-uri.tooltip")); + } + }); + } + + /** + * Initializes the page. + */ + private void pageInit() { + setLayout(new BorderLayout(12, 12)); + add(createProjectInsertPanel(), BorderLayout.CENTER); + } + + /** + * Creates the main panel. + * + * @return The main panel + */ + private JComponent createProjectInsertPanel() { + JComponent projectInsertPanel = new JPanel(new GridBagLayout()); + + requestURITextField = new JTextField(); + requestURITextField.setEditable(false); + + startTimeLabel = new JLabel(); + + progressBar = new JProgressBar(0, 1); + progressBar.setStringPainted(true); + progressBar.setValue(0); + + final JLabel projectInformationLabel = new JLabel("" + I18n.getMessage("jsite.insert.project-information") + ""); + projectInsertPanel.add(projectInformationLabel, new GridBagConstraints(0, 0, 2, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); + final JLabel requestURILabel = new JLabel(I18n.getMessage("jsite.insert.request-uri") + ":"); + projectInsertPanel.add(requestURILabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 18, 0, 0), 0, 0)); + projectInsertPanel.add(requestURITextField, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0)); + final JLabel startTimeLeftLabel = new JLabel(I18n.getMessage("jsite.insert.start-time") + ":"); + projectInsertPanel.add(startTimeLeftLabel, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 18, 0, 0), 0, 0)); + projectInsertPanel.add(startTimeLabel, new GridBagConstraints(1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0)); + final JLabel progressLabel = new JLabel(I18n.getMessage("jsite.insert.progress") + ":"); + projectInsertPanel.add(progressLabel, new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 18, 0, 0), 0, 0)); + projectInsertPanel.add(progressBar, new GridBagConstraints(1, 3, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0)); + projectInsertPanel.add(new JButton(copyURIAction), new GridBagConstraints(0, 4, 2, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.NONE, new Insets(12, 18, 0, 0), 0, 0)); + + I18nContainer.getInstance().registerRunnable(new Runnable() { + ++ @Override + @SuppressWarnings("synthetic-access") + public void run() { + projectInformationLabel.setText("" + I18n.getMessage("jsite.insert.project-information") + ""); + requestURILabel.setText(I18n.getMessage("jsite.insert.request-uri") + ":"); + startTimeLeftLabel.setText(I18n.getMessage("jsite.insert.start-time") + ":"); + if (startTime != 0) { + startTimeLabel.setText(DateFormat.getDateTimeInstance().format(new Date(startTime))); + } else { + startTimeLabel.setText(""); + } + progressLabel.setText(I18n.getMessage("jsite.insert.progress") + ":"); + } + }); + + return projectInsertPanel; + } + + /** + * {@inheritDoc} + */ + @Override + public void pageAdded(TWizard wizard) { + this.wizard.setPreviousName(I18n.getMessage("jsite.wizard.previous")); + this.wizard.setPreviousEnabled(false); + this.wizard.setNextName(I18n.getMessage("jsite.general.cancel")); + this.wizard.setQuitName(I18n.getMessage("jsite.wizard.quit")); + } + + /** + * Starts the insert. + */ + public void startInsert() { + running = true; + copyURIAction.setEnabled(false); + progressBar.setValue(0); + progressBar.setString(I18n.getMessage("jsite.insert.starting")); + progressBar.setFont(progressBar.getFont().deriveFont(Font.PLAIN)); + projectInserter.start(new ProgressListener() { + ++ @Override + public void onProgress(final long copied, final long length) { + SwingUtilities.invokeLater(new Runnable() { + + /** + * {@inheritDoc} + */ ++ @Override + @SuppressWarnings("synthetic-access") + public void run() { + int divisor = 1; + while (((copied / divisor) > Integer.MAX_VALUE) || ((length / divisor) > Integer.MAX_VALUE)) { + divisor *= 10; + } + progressBar.setMaximum((int) (length / divisor)); + progressBar.setValue((int) (copied / divisor)); + progressBar.setString("Uploaded: " + copied + " / " + length); + } + }); + } + }); + } + + /** + * Stops the currently running insert. + */ + public void stopInsert() { + if (running) { + wizard.setNextEnabled(false); + projectInserter.stop(); + } + } + + /** + * Returns whether the insert is currently running. + * + * @return {@code true} if the insert is currently running, {@code false} + * otherwise + */ + public boolean isRunning() { + return running; + } + + /** + * Sets the project to insert. + * + * @param project + * The project to insert + */ + public void setProject(final Project project) { + projectInserter.setProject(project); + SwingUtilities.invokeLater(new Runnable() { + ++ @Override + @SuppressWarnings("synthetic-access") + public void run() { + requestURITextField.setText(project.getFinalRequestURI(1)); + } + }); + } + + /** + * Sets the freenet interface to use. + * + * @param freenetInterface + * The freenet interface to use + */ + public void setFreenetInterface(Freenet7Interface freenetInterface) { + projectInserter.setFreenetInterface(freenetInterface); + } + + /** + * Sets the project inserter’s temp directory. + * + * @see ProjectInserter#setTempDirectory(String) + * @param tempDirectory + * The temp directory to use, or {@code null} to use the system + * default + */ + public void setTempDirectory(String tempDirectory) { + projectInserter.setTempDirectory(tempDirectory); + } + + /** + * Returns whether the “copy URI to clipboard” button was used. + * + * @return {@code true} if an URI was copied to clipboard, {@code false} + * otherwise + */ + public boolean wasUriCopied() { + return uriCopied; + } + + /** + * Sets whether to use the “early encode“ flag for the insert. + * + * @param useEarlyEncode + * {@code true} to set the “early encode” flag for the insert, + * {@code false} otherwise + */ + public void setUseEarlyEncode(boolean useEarlyEncode) { + projectInserter.setUseEarlyEncode(useEarlyEncode); + } + + /** + * Sets the insert priority. + * + * @param priority + * The insert priority + */ + public void setPriority(PriorityClass priority) { + projectInserter.setPriority(priority); + } + + /** + * Sets the manifest putter to use for the insert. + * + * @see ProjectInserter#setManifestPutter(ManifestPutter) + * @param manifestPutter + * The manifest putter + */ + public void setManifestPutter(ManifestPutter manifestPutter) { + projectInserter.setManifestPutter(manifestPutter); + } + + // + // INTERFACE InsertListener + // + + /** + * {@inheritDoc} + */ ++ @Override + public void projectInsertStarted(final Project project) { + + SwingUtilities.invokeLater(new Runnable() { + ++ @Override + @SuppressWarnings("synthetic-access") + public void run() { + startTimeLabel.setText(DateFormat.getDateTimeInstance().format(new Date())); + } + }); + } + + /** + * {@inheritDoc} + */ ++ @Override + public void projectUploadFinished(Project project) { + startTime = System.currentTimeMillis(); + SwingUtilities.invokeLater(new Runnable() { + ++ @Override + @SuppressWarnings("synthetic-access") + public void run() { + progressBar.setString(I18n.getMessage("jsite.insert.starting")); + progressBar.setValue(0); + } + }); + } + + /** + * {@inheritDoc} + */ ++ @Override + public void projectURIGenerated(Project project, final String uri) { + SwingUtilities.invokeLater(new Runnable() { + ++ @Override + @SuppressWarnings("synthetic-access") + public void run() { + copyURIAction.setEnabled(true); + requestURITextField.setText(uri); + } + }); + logger.log(Level.FINEST, "Insert generated URI: " + uri); + int slash = uri.indexOf('/'); + slash = uri.indexOf('/', slash + 1); + int secondSlash = uri.indexOf('/', slash + 1); + if (secondSlash == -1) { + secondSlash = uri.length(); + } + String editionNumber = uri.substring(slash + 1, secondSlash); + logger.log(Level.FINEST, "Extracted edition number: " + editionNumber); + int edition = -1; + try { + edition = Integer.valueOf(editionNumber); + } catch (NumberFormatException nfe1) { + /* ignore. */ + } + logger.log(Level.FINEST, "Insert edition: " + edition + ", Project edition: " + project.getEdition()); + if ((edition != -1) && (edition == project.getEdition())) { + JOptionPane.showMessageDialog(this, I18n.getMessage("jsite.insert.reinserted-edition"), I18n.getMessage("jsite.insert.reinserted-edition.title"), JOptionPane.INFORMATION_MESSAGE); + } + } + + /** + * {@inheritDoc} + */ ++ @Override + public void projectInsertProgress(Project project, final int succeeded, final int failed, final int fatal, final int total, final boolean finalized) { + insertedBlocks = succeeded; + SwingUtilities.invokeLater(new Runnable() { + ++ @Override + @SuppressWarnings("synthetic-access") + public void run() { + if (total == 0) { + return; + } + progressBar.setMaximum(total); + progressBar.setValue(succeeded + failed + fatal); + int progress = (succeeded + failed + fatal) * 100 / total; + StringBuilder progressString = new StringBuilder(); + progressString.append(progress).append("% ("); + progressString.append(succeeded + failed + fatal).append('/').append(total); + progressString.append(") ("); + progressString.append(getTransferRate()); + progressString.append(' ').append(I18n.getMessage("jsite.insert.k-per-s")).append(')'); + progressBar.setString(progressString.toString()); + if (finalized) { + progressBar.setFont(progressBar.getFont().deriveFont(Font.BOLD)); + } + } + }); + } + + /** + * {@inheritDoc} + */ ++ @Override + public void projectInsertFinished(Project project, boolean success, Throwable cause) { + running = false; + if (success) { + String copyURILabel = I18n.getMessage("jsite.insert.okay-copy-uri"); + int selectedValue = JOptionPane.showOptionDialog(this, I18n.getMessage("jsite.insert.inserted"), I18n.getMessage("jsite.insert.done.title"), 0, JOptionPane.INFORMATION_MESSAGE, null, new Object[] { I18n.getMessage("jsite.general.ok"), copyURILabel }, copyURILabel); + if (selectedValue == 1) { + actionCopyURI(); + } + } else { + if (cause == null) { + JOptionPane.showMessageDialog(this, I18n.getMessage("jsite.insert.insert-failed"), I18n.getMessage("jsite.insert.insert-failed.title"), JOptionPane.ERROR_MESSAGE); + } else { + if (cause instanceof AbortedException) { + JOptionPane.showMessageDialog(this, I18n.getMessage("jsite.insert.insert-aborted"), I18n.getMessage("jsite.insert.insert-aborted.title"), JOptionPane.INFORMATION_MESSAGE); + } else { + JOptionPane.showMessageDialog(this, MessageFormat.format(I18n.getMessage("jsite.insert.insert-failed-with-cause"), cause.getMessage()), I18n.getMessage("jsite.insert.insert-failed.title"), JOptionPane.ERROR_MESSAGE); + } + } + } + SwingUtilities.invokeLater(new Runnable() { + ++ @Override + @SuppressWarnings("synthetic-access") + public void run() { + progressBar.setValue(progressBar.getMaximum()); + progressBar.setString(I18n.getMessage("jsite.insert.done") + " (" + getTransferRate() + " " + I18n.getMessage("jsite.insert.k-per-s") + ")"); + wizard.setNextName(I18n.getMessage("jsite.wizard.next")); + wizard.setNextEnabled(true); + wizard.setQuitEnabled(true); + } + }); + } + + // + // ACTIONS + // + + /** + * Copies the request URI of the project to the clipboard. + */ + private void actionCopyURI() { + uriCopied = true; + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + clipboard.setContents(new StringSelection(requestURITextField.getText()), this); + } + + /** + * Formats the given number so that it always has the the given number of + * fractional digits. + * + * @param number + * The number to format + * @param digits + * The number of fractional digits + * @return The formatted number + */ - private String formatNumber(double number, int digits) { ++ private static String formatNumber(double number, int digits) { + int multiplier = (int) Math.pow(10, digits); + String formattedNumber = String.valueOf((int) (number * multiplier) / (double) multiplier); + if (formattedNumber.indexOf('.') == -1) { + formattedNumber += '.'; + for (int digit = 0; digit < digits; digit++) { + formattedNumber += "0"; + } + } + return formattedNumber; + } + + /** + * Returns the formatted transfer rate at this point. + * + * @return The formatted transfer rate + */ + private String getTransferRate() { + return formatNumber(insertedBlocks * 32.0 / ((System.currentTimeMillis() - startTime) / 1000), 1); + } + + // + // INTERFACE ClipboardOwner + // + + /** + * {@inheritDoc} + */ ++ @Override + public void lostOwnership(Clipboard clipboard, Transferable contents) { + /* ignore. */ + } + + } diff --cc src/main/java/de/todesbaum/jsite/gui/ProjectPage.java index 0000000,9345dbf..a072423 mode 000000,100644..100644 --- a/src/main/java/de/todesbaum/jsite/gui/ProjectPage.java +++ b/src/main/java/de/todesbaum/jsite/gui/ProjectPage.java @@@ -1,0 -1,724 +1,739 @@@ + /* + * jSite - ProjectPage.java - Copyright © 2006–2012 David Roden + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + package de.todesbaum.jsite.gui; + + import java.awt.BorderLayout; + import java.awt.Dimension; + import java.awt.FlowLayout; + import java.awt.GridBagConstraints; + import java.awt.GridBagLayout; + import java.awt.Insets; + import java.awt.Toolkit; + import java.awt.datatransfer.Clipboard; + import java.awt.datatransfer.ClipboardOwner; + import java.awt.datatransfer.StringSelection; + import java.awt.datatransfer.Transferable; + import java.awt.event.ActionEvent; + import java.awt.event.KeyEvent; + import java.io.IOException; + import java.text.MessageFormat; + + import javax.swing.AbstractAction; + import javax.swing.Action; + import javax.swing.JButton; + import javax.swing.JComponent; + import javax.swing.JFileChooser; + import javax.swing.JLabel; + import javax.swing.JList; + import javax.swing.JOptionPane; + import javax.swing.JPanel; + import javax.swing.JScrollPane; + import javax.swing.JTextField; + import javax.swing.ListSelectionModel; + import javax.swing.border.EmptyBorder; + import javax.swing.event.DocumentEvent; + import javax.swing.event.DocumentListener; + import javax.swing.event.ListSelectionEvent; + import javax.swing.event.ListSelectionListener; + import javax.swing.text.AbstractDocument; + import javax.swing.text.AttributeSet; + import javax.swing.text.BadLocationException; + import javax.swing.text.Document; + import javax.swing.text.DocumentFilter; + + import net.pterodactylus.util.swing.SortedListModel; + import de.todesbaum.jsite.application.Freenet7Interface; + import de.todesbaum.jsite.application.KeyDialog; + import de.todesbaum.jsite.application.Project; + import de.todesbaum.jsite.i18n.I18n; + import de.todesbaum.jsite.i18n.I18nContainer; + import de.todesbaum.util.swing.TLabel; + import de.todesbaum.util.swing.TWizard; + import de.todesbaum.util.swing.TWizardPage; + + /** + * Wizard page that lets the user manage his projects and start inserts. + * + * @author David ‘Bombe’ Roden <bombe@freenetproject.org> + */ + public class ProjectPage extends TWizardPage implements ListSelectionListener, DocumentListener, ClipboardOwner { + + /** The freenet interface. */ + private Freenet7Interface freenetInterface; + + /** The “browse” action. */ + private Action projectLocalPathBrowseAction; + + /** The “add project” action. */ + private Action projectAddAction; + + /** The “delete project” action. */ + private Action projectDeleteAction; + + /** The “clone project” action. */ + private Action projectCloneAction; + + /** The “manage keys” action. */ + private Action projectManageKeysAction; + + /** The “copy URI” action. */ + private Action projectCopyURIAction; + + /** The “reset edition” action. */ + private Action projectResetEditionAction; + + /** The file chooser. */ + private JFileChooser pathChooser; + + /** The project list model. */ + private SortedListModel projectListModel; + + /** The project list scroll pane. */ + private JScrollPane projectScrollPane; + + /** The project list. */ + private JList projectList; + + /** The project name textfield. */ + private JTextField projectNameTextField; + + /** The project description textfield. */ + private JTextField projectDescriptionTextField; + + /** The local path textfield. */ + private JTextField projectLocalPathTextField; + + /** The textfield for the complete URI. */ + private JTextField projectCompleteUriTextField; + + /** The project path textfield. */ + private JTextField projectPathTextField; + + /** Whether the “copy URI to clipboard” action was used. */ + private boolean uriCopied; + + /** + * Creates a new project page. + * + * @param wizard + * The wizard this page belongs to + */ + public ProjectPage(final TWizard wizard) { + super(wizard); + setLayout(new BorderLayout(12, 12)); + dialogInit(); + setHeading(I18n.getMessage("jsite.project.heading")); + setDescription(I18n.getMessage("jsite.project.description")); + + I18nContainer.getInstance().registerRunnable(new Runnable() { + ++ @Override + public void run() { + setHeading(I18n.getMessage("jsite.project.heading")); + setDescription(I18n.getMessage("jsite.project.description")); + } + }); + } + + /** + * Initializes the page. + */ + private void dialogInit() { + createActions(); + + pathChooser = new JFileChooser(); + projectListModel = new SortedListModel(); + projectList = new JList(projectListModel); + projectList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + projectList.addListSelectionListener(this); + + add(projectScrollPane = new JScrollPane(projectList), BorderLayout.LINE_START); + projectScrollPane.setPreferredSize(new Dimension(150, projectList.getPreferredSize().height)); + add(createInformationPanel(), BorderLayout.CENTER); + } + + /** + * {@inheritDoc} + */ + @Override + public void pageAdded(TWizard wizard) { + super.pageAdded(wizard); + projectList.clearSelection(); + this.wizard.setPreviousName(I18n.getMessage("jsite.menu.nodes.manage-nodes")); + this.wizard.setNextName(I18n.getMessage("jsite.wizard.next")); + this.wizard.setQuitName(I18n.getMessage("jsite.wizard.quit")); + this.wizard.setNextEnabled(false); + } + + /** + * Adds the given listener to the list of listeners. + * + * @param listener + * The listener to add + */ + public void addListSelectionListener(ListSelectionListener listener) { + projectList.addListSelectionListener(listener); + } + + /** + * Removes the given listener from the list of listeners. + * + * @param listener + * The listener to remove + */ + public void removeListSelectionListener(ListSelectionListener listener) { + projectList.removeListSelectionListener(listener); + } + + /** + * Creates all actions. + */ + private void createActions() { + projectLocalPathBrowseAction = new AbstractAction(I18n.getMessage("jsite.project.action.browse")) { + ++ @Override + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + actionLocalPathBrowse(); + } + }; + projectLocalPathBrowseAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project.action.browse.tooltip")); + projectLocalPathBrowseAction.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_B); + projectLocalPathBrowseAction.setEnabled(false); + + projectAddAction = new AbstractAction(I18n.getMessage("jsite.project.action.add-project")) { + ++ @Override + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + actionAdd(); + } + }; + projectAddAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project.action.add-project.tooltip")); + projectAddAction.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_A); + + projectDeleteAction = new AbstractAction(I18n.getMessage("jsite.project.action.delete-project")) { + ++ @Override + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + actionDelete(); + } + }; + projectDeleteAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project.action.delete-project.tooltip")); + projectDeleteAction.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_D); + projectDeleteAction.setEnabled(false); + + projectCloneAction = new AbstractAction(I18n.getMessage("jsite.project.action.clone-project")) { + ++ @Override + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + actionClone(); + } + }; + projectCloneAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project.action.clone-project.tooltip")); + projectCloneAction.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_L); + projectCloneAction.setEnabled(false); + + projectCopyURIAction = new AbstractAction(I18n.getMessage("jsite.project.action.copy-uri")) { + ++ @Override + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + actionCopyURI(); + } + }; + projectCopyURIAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project.action.copy-uri.tooltip")); + projectCopyURIAction.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_U); + projectCopyURIAction.setEnabled(false); + + projectManageKeysAction = new AbstractAction(I18n.getMessage("jsite.project.action.manage-keys")) { + ++ @Override + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + actionManageKeys(); + } + }; + projectManageKeysAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project.action.manage-keys.tooltip")); + projectManageKeysAction.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_M); + projectManageKeysAction.setEnabled(false); + + projectResetEditionAction = new AbstractAction(I18n.getMessage("jsite.project.action.reset-edition")) { + ++ @Override + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + actionResetEdition(); + } + }; + projectResetEditionAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project.action.reset-edition.tooltip")); + projectResetEditionAction.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_R); + projectResetEditionAction.setEnabled(false); + + I18nContainer.getInstance().registerRunnable(new Runnable() { + ++ @Override + @SuppressWarnings("synthetic-access") + public void run() { + projectLocalPathBrowseAction.putValue(Action.NAME, I18n.getMessage("jsite.project.action.browse")); + projectLocalPathBrowseAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project.action.browse.tooltip")); + projectAddAction.putValue(Action.NAME, I18n.getMessage("jsite.project.action.add-project")); + projectAddAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project.action.add-project.tooltip")); + projectDeleteAction.putValue(Action.NAME, I18n.getMessage("jsite.project.action.delete-project")); + projectDeleteAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project.action.delete-project.tooltip")); + projectCloneAction.putValue(Action.NAME, I18n.getMessage("jsite.project.action.clone-project")); + projectCloneAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project.action.clone-project.tooltip")); + projectCopyURIAction.putValue(Action.NAME, I18n.getMessage("jsite.project.action.copy-uri")); + projectCopyURIAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project.action.copy-uri.tooltip")); + projectManageKeysAction.putValue(Action.NAME, I18n.getMessage("jsite.project.action.manage-keys")); + projectManageKeysAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project.action.manage-keys.tooltip")); + projectResetEditionAction.putValue(Action.NAME, I18n.getMessage("jsite.project.action.reset-edition")); + projectResetEditionAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project.action.reset-edition.tooltip")); + pathChooser.setApproveButtonText(I18n.getMessage("jsite.project.action.browse.choose")); + } + }); + } + + /** + * Creates the information panel. + * + * @return The information panel + */ + private JComponent createInformationPanel() { + JPanel informationPanel = new JPanel(new BorderLayout(12, 12)); + + JPanel informationTable = new JPanel(new GridBagLayout()); + + JPanel functionButtons = new JPanel(new FlowLayout(FlowLayout.LEADING, 12, 12)); + functionButtons.setBorder(new EmptyBorder(-12, -12, -12, -12)); + functionButtons.add(new JButton(projectAddAction)); + functionButtons.add(new JButton(projectDeleteAction)); + functionButtons.add(new JButton(projectCloneAction)); + functionButtons.add(new JButton(projectManageKeysAction)); + + informationPanel.add(functionButtons, BorderLayout.PAGE_START); + informationPanel.add(informationTable, BorderLayout.CENTER); + + final JLabel projectInformationLabel = new JLabel("" + I18n.getMessage("jsite.project.project.information") + ""); + informationTable.add(projectInformationLabel, new GridBagConstraints(0, 0, 3, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); + + projectNameTextField = new JTextField(); + projectNameTextField.getDocument().putProperty("name", "project.name"); + projectNameTextField.getDocument().addDocumentListener(this); + projectNameTextField.setEnabled(false); + + final TLabel projectNameLabel = new TLabel(I18n.getMessage("jsite.project.project.name") + ":", KeyEvent.VK_N, projectNameTextField); + informationTable.add(projectNameLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 18, 0, 0), 0, 0)); + informationTable.add(projectNameTextField, new GridBagConstraints(1, 1, 2, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0)); + + projectDescriptionTextField = new JTextField(); + projectDescriptionTextField.getDocument().putProperty("name", "project.description"); + projectDescriptionTextField.getDocument().addDocumentListener(this); + projectDescriptionTextField.setEnabled(false); + + final TLabel projectDescriptionLabel = new TLabel(I18n.getMessage("jsite.project.project.description") + ":", KeyEvent.VK_D, projectDescriptionTextField); + informationTable.add(projectDescriptionLabel, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 18, 0, 0), 0, 0)); + informationTable.add(projectDescriptionTextField, new GridBagConstraints(1, 2, 2, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0)); + + projectLocalPathTextField = new JTextField(); + projectLocalPathTextField.getDocument().putProperty("name", "project.localpath"); + projectLocalPathTextField.getDocument().addDocumentListener(this); + projectLocalPathTextField.setEnabled(false); + + final TLabel projectLocalPathLabel = new TLabel(I18n.getMessage("jsite.project.project.local-path") + ":", KeyEvent.VK_L, projectLocalPathTextField); + informationTable.add(projectLocalPathLabel, new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 18, 0, 0), 0, 0)); + informationTable.add(projectLocalPathTextField, new GridBagConstraints(1, 3, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0)); + informationTable.add(new JButton(projectLocalPathBrowseAction), new GridBagConstraints(2, 3, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0)); + + final JLabel projectAddressLabel = new JLabel("" + I18n.getMessage("jsite.project.project.address") + ""); + informationTable.add(projectAddressLabel, new GridBagConstraints(0, 4, 3, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(12, 0, 0, 0), 0, 0)); + + projectPathTextField = new JTextField(); + projectPathTextField.getDocument().putProperty("name", "project.path"); + projectPathTextField.getDocument().addDocumentListener(this); + ((AbstractDocument) projectPathTextField.getDocument()).setDocumentFilter(new DocumentFilter() { + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("synthetic-access") + public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException { + super.insertString(fb, offset, string.replaceAll("/", ""), attr); + updateCompleteURI(); + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("synthetic-access") + public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException { + super.replace(fb, offset, length, text.replaceAll("/", ""), attrs); + updateCompleteURI(); + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("synthetic-access") + public void remove(FilterBypass fb, int offset, int length) throws BadLocationException { + super.remove(fb, offset, length); + updateCompleteURI(); + } + }); + projectPathTextField.setEnabled(false); + + final TLabel projectPathLabel = new TLabel(I18n.getMessage("jsite.project.project.path") + ":", KeyEvent.VK_P, projectPathTextField); + informationTable.add(projectPathLabel, new GridBagConstraints(0, 5, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 18, 0, 0), 0, 0)); + informationTable.add(projectPathTextField, new GridBagConstraints(1, 5, 2, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0)); + + projectCompleteUriTextField = new JTextField(); + projectCompleteUriTextField.setEditable(false); + final TLabel projectUriLabel = new TLabel(I18n.getMessage("jsite.project.project.uri") + ":", KeyEvent.VK_U, projectCompleteUriTextField); + informationTable.add(projectUriLabel, new GridBagConstraints(0, 6, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 18, 0, 0), 0, 0)); + informationTable.add(projectCompleteUriTextField, new GridBagConstraints(1, 6, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0)); + informationTable.add(new JButton(projectCopyURIAction), new GridBagConstraints(2, 6, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0)); + + I18nContainer.getInstance().registerRunnable(new Runnable() { + ++ @Override + public void run() { + projectInformationLabel.setText("" + I18n.getMessage("jsite.project.project.information") + ""); + projectNameLabel.setText(I18n.getMessage("jsite.project.project.name") + ":"); + projectDescriptionLabel.setText(I18n.getMessage("jsite.project.project.description") + ":"); + projectLocalPathLabel.setText(I18n.getMessage("jsite.project.project.local-path") + ":"); + projectAddressLabel.setText("" + I18n.getMessage("jsite.project.project.address") + ""); + projectPathLabel.setText(I18n.getMessage("jsite.project.project.path") + ":"); + projectUriLabel.setText(I18n.getMessage("jsite.project.project.uri") + ":"); + } + }); + + return informationPanel; + } + + /** + * Sets the project list. + * + * @param projects + * The list of projects + */ + public void setProjects(Project[] projects) { + projectListModel.clear(); + for (Project project : projects) { + projectListModel.add(project); + } + } + + /** + * Returns the list of projects. + * + * @return The list of projects + */ + public Project[] getProjects() { + return projectListModel.toArray(new Project[projectListModel.size()]); + } + + /** + * Sets the freenet interface to use. + * + * @param freenetInterface + * The freenetInterface to use + */ + public void setFreenetInterface(Freenet7Interface freenetInterface) { + this.freenetInterface = freenetInterface; + } + + /** + * Returns the currently selected project. + * + * @return The currently selected project + */ + public Project getSelectedProject() { + return (Project) projectList.getSelectedValue(); + } + + /** + * Returns whether the “copy URI to clipboard” button was used. + * + * @return {@code true} if the “copy URI to clipboard” button was used, + * {@code false} otherwise + */ + public boolean wasUriCopied() { + return uriCopied; + } + + /** + * Updates the currently selected project with changed information from a + * textfield. + * + * @param documentEvent + * The document event to process + */ + private void setTextField(DocumentEvent documentEvent) { + Document document = documentEvent.getDocument(); + String propertyName = (String) document.getProperty("name"); + Project project = (Project) projectList.getSelectedValue(); + if (project == null) { + return; + } + try { + String text = document.getText(0, document.getLength()).trim(); + if ("project.name".equals(propertyName)) { + project.setName(text); + projectList.repaint(); + } else if ("project.description".equals(propertyName)) { + project.setDescription(text); + } else if ("project.localpath".equals(propertyName)) { + project.setLocalPath(text); + } else if ("project.privatekey".equals(propertyName)) { + project.setInsertURI(text); + } else if ("project.publickey".equals(propertyName)) { + project.setRequestURI(text); + } else if ("project.path".equals(propertyName)) { + project.setPath(text); + } + } catch (BadLocationException e) { + /* ignore. */ + } + } + + // + // ACTIONS + // + + /** + * Lets the user choose a local path for a project. + */ + private void actionLocalPathBrowse() { + Project project = (Project) projectList.getSelectedValue(); + if (project == null) { + return; + } + pathChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + if (pathChooser.showDialog(this, I18n.getMessage("jsite.project.action.browse.choose")) == JFileChooser.APPROVE_OPTION) { + projectLocalPathTextField.setText(pathChooser.getSelectedFile().getPath()); + } + } + + /** + * Adds a new project. + */ + private void actionAdd() { + String[] keyPair = null; + if (!freenetInterface.hasNode()) { + JOptionPane.showMessageDialog(this, I18n.getMessage("jsite.project-files.no-node-selected"), null, JOptionPane.ERROR_MESSAGE); + return; + } + try { + keyPair = freenetInterface.generateKeyPair(); + } catch (IOException ioe1) { + JOptionPane.showMessageDialog(this, MessageFormat.format(I18n.getMessage("jsite.project.keygen.io-error"), ioe1.getMessage()), null, JOptionPane.ERROR_MESSAGE); + return; + } + Project newProject = new Project(); + newProject.setName(I18n.getMessage("jsite.project.new-project.name")); + newProject.setInsertURI(keyPair[0]); + newProject.setRequestURI(keyPair[1]); + newProject.setEdition(-1); + newProject.setPath(""); + projectListModel.add(newProject); + projectScrollPane.revalidate(); + projectScrollPane.repaint(); + projectList.setSelectedIndex(projectListModel.indexOf(newProject)); + } + + /** + * Deletes the currently selected project. + */ + private void actionDelete() { + int selectedIndex = projectList.getSelectedIndex(); + if (selectedIndex > -1) { + if (JOptionPane.showConfirmDialog(this, MessageFormat.format(I18n.getMessage("jsite.project.action.delete-project.confirm"), ((Project) projectList.getSelectedValue()).getName()), null, JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE) == JOptionPane.OK_OPTION) { + projectListModel.remove(selectedIndex); + projectList.clearSelection(); + if (projectListModel.getSize() != 0) { + projectList.setSelectedIndex(Math.min(selectedIndex, projectListModel.getSize() - 1)); + } + } + } + } + + /** + * Clones the currently selected project. + */ + private void actionClone() { + int selectedIndex = projectList.getSelectedIndex(); + if (selectedIndex > -1) { + Project newProject = new Project((Project) projectList.getSelectedValue()); + newProject.setName(MessageFormat.format(I18n.getMessage("jsite.project.action.clone-project.copy"), newProject.getName())); + projectListModel.add(newProject); + projectList.setSelectedIndex(projectListModel.indexOf(newProject)); + } + } + + /** + * Copies the request URI of the currently selected project to the + * clipboard. + */ + private void actionCopyURI() { + int selectedIndex = projectList.getSelectedIndex(); + if (selectedIndex > -1) { + Project selectedProject = (Project) projectList.getSelectedValue(); + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + clipboard.setContents(new StringSelection(selectedProject.getFinalRequestURI(0)), this); + uriCopied = true; + } + } + + /** + * Opens a {@link KeyDialog} and lets the user manipulate the keys of the + * project. + */ + private void actionManageKeys() { + int selectedIndex = projectList.getSelectedIndex(); + if (selectedIndex > -1) { + Project selectedProject = (Project) projectList.getSelectedValue(); + KeyDialog keyDialog = new KeyDialog(freenetInterface, wizard); + keyDialog.setPrivateKey(selectedProject.getInsertURI()); + keyDialog.setPublicKey(selectedProject.getRequestURI()); + keyDialog.setVisible(true); + if (!keyDialog.wasCancelled()) { + String originalPublicKey = selectedProject.getRequestURI(); + String originalPrivateKey = selectedProject.getInsertURI(); + selectedProject.setInsertURI(keyDialog.getPrivateKey()); + selectedProject.setRequestURI(keyDialog.getPublicKey()); + if (!originalPublicKey.equals(selectedProject.getRequestURI()) || !originalPrivateKey.equals(selectedProject.getInsertURI())) { + selectedProject.setEdition(-1); + } + updateCompleteURI(); + } + } + } + + /** + * Resets the edition of the currently selected project. + */ + private void actionResetEdition() { + if (JOptionPane.showConfirmDialog(this, I18n.getMessage("jsite.project.warning.reset-edition"), null, JOptionPane.OK_CANCEL_OPTION) == JOptionPane.CANCEL_OPTION) { + return; + } + int selectedIndex = projectList.getSelectedIndex(); + if (selectedIndex > -1) { + Project selectedProject = (Project) projectList.getSelectedValue(); + selectedProject.setEdition(-1); + updateCompleteURI(); + } + } + + /** + * Updates the complete URI text field. + */ + private void updateCompleteURI() { + int selectedIndex = projectList.getSelectedIndex(); + if (selectedIndex > -1) { + Project selectedProject = (Project) projectList.getSelectedValue(); + projectCompleteUriTextField.setText(selectedProject.getFinalRequestURI(0)); + } + } + + // + // INTERFACE ListSelectionListener + // + + /** + * {@inheritDoc} + */ ++ @Override + public void valueChanged(ListSelectionEvent listSelectionEvent) { + int selectedRow = projectList.getSelectedIndex(); + Project selectedProject = (Project) projectList.getSelectedValue(); + projectNameTextField.setEnabled(selectedRow > -1); + projectDescriptionTextField.setEnabled(selectedRow > -1); + projectLocalPathTextField.setEnabled(selectedRow > -1); + projectPathTextField.setEnabled(selectedRow > -1); + projectLocalPathBrowseAction.setEnabled(selectedRow > -1); + projectDeleteAction.setEnabled(selectedRow > -1); + projectCloneAction.setEnabled(selectedRow > -1); + projectCopyURIAction.setEnabled(selectedRow > -1); + projectManageKeysAction.setEnabled(selectedRow > -1); + projectResetEditionAction.setEnabled(selectedRow > -1); + if (selectedRow > -1) { + projectNameTextField.setText(selectedProject.getName()); + projectDescriptionTextField.setText(selectedProject.getDescription()); + projectLocalPathTextField.setText(selectedProject.getLocalPath()); + projectPathTextField.setText(selectedProject.getPath()); + projectCompleteUriTextField.setText("freenet:" + selectedProject.getFinalRequestURI(0)); + } else { + projectNameTextField.setText(""); + projectDescriptionTextField.setText(""); + projectLocalPathTextField.setText(""); + projectPathTextField.setText(""); + projectCompleteUriTextField.setText(""); + } + } + + // + // INTERFACE ChangeListener + // + + // + // INTERFACE DocumentListener + // + + /** + * {@inheritDoc} + */ ++ @Override + public void insertUpdate(DocumentEvent documentEvent) { + setTextField(documentEvent); + } + + /** + * {@inheritDoc} + */ ++ @Override + public void removeUpdate(DocumentEvent documentEvent) { + setTextField(documentEvent); + } + + /** + * {@inheritDoc} + */ ++ @Override + public void changedUpdate(DocumentEvent documentEvent) { + setTextField(documentEvent); + } + + // + // INTERFACE ClipboardOwner + // + + /** + * {@inheritDoc} + */ ++ @Override + public void lostOwnership(Clipboard clipboard, Transferable contents) { + /* ignore. */ + } + + } diff --cc src/main/java/de/todesbaum/jsite/i18n/I18nContainer.java index 0000000,da2e0c3..9683dd3 mode 000000,100644..100644 --- a/src/main/java/de/todesbaum/jsite/i18n/I18nContainer.java +++ b/src/main/java/de/todesbaum/jsite/i18n/I18nContainer.java @@@ -1,0 -1,90 +1,91 @@@ + /* + * jSite - I18nContainer.java - Copyright © 2007–2012 David Roden + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + package de.todesbaum.jsite.i18n; + + import java.util.ArrayList; + import java.util.Collections; + import java.util.Iterator; + import java.util.List; + + /** + * Container that collects {@link Runnable}s that change the texts of GUI + * components when the current locale has changed. + * + * @author David ‘Bombe’ Roden <bombe@freenetproject.org> + */ + public class I18nContainer implements Iterable { + + /** The container singleton. */ + private static final I18nContainer singleton = new I18nContainer(); + + /** The list of runnables that change texts. */ + private final List i18nRunnables = Collections.synchronizedList(new ArrayList()); + + /** + * The list of runnables that change texts and run after + * {@link #i18nRunnables}. + */ + private final List i18nPostRunnables = Collections.synchronizedList(new ArrayList()); + + /** + * Returns the singleton instance. + * + * @return The singleton instance + */ + public static I18nContainer getInstance() { + return singleton; + } + + /** + * Registers an i18n runnable that is run when the current locale has + * changed. + * + * @param i18nRunnable + * The runnable to register + */ + public void registerRunnable(Runnable i18nRunnable) { + i18nRunnables.add(i18nRunnable); + } + + /** + * Registers a {@link Runnable} that changes texts when the current locale + * has changed and runs after {@link #i18nRunnables} have run. + * + * @param i18nPostRunnable + * The runnable to register + */ + public void registerPostRunnable(Runnable i18nPostRunnable) { + i18nPostRunnables.add(i18nPostRunnable); + } + + /** + * {@inheritDoc} + *

+ * Returns a combined list of {@link #i18nRunnables} and + * {@link #i18nPostRunnables}, in that order. + */ ++ @Override + public Iterator iterator() { + List allRunnables = new ArrayList(); + allRunnables.addAll(i18nRunnables); + allRunnables.addAll(i18nPostRunnables); + return allRunnables.iterator(); + } + + } diff --cc src/main/java/de/todesbaum/jsite/main/CLI.java index 0000000,0cf9cc4..bbe76c7 mode 000000,100644..100644 --- a/src/main/java/de/todesbaum/jsite/main/CLI.java +++ b/src/main/java/de/todesbaum/jsite/main/CLI.java @@@ -1,0 -1,297 +1,303 @@@ + /* + * jSite - CLI.java - Copyright © 2006–2012 David Roden + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + package de.todesbaum.jsite.main; + + import java.io.PrintWriter; + + import net.pterodactylus.util.io.StreamCopier.ProgressListener; + import de.todesbaum.jsite.application.Freenet7Interface; + import de.todesbaum.jsite.application.InsertListener; + import de.todesbaum.jsite.application.Node; + import de.todesbaum.jsite.application.Project; + import de.todesbaum.jsite.application.ProjectInserter; + + /** + * Command-line interface for jSite. + * + * @author David ‘Bombe’ Roden <bombe@freenetproject.org> + */ + public class CLI implements InsertListener { + + /** Object used for synchronization. */ + private Object lockObject = new Object(); + + /** Writer for the console. */ + private PrintWriter outputWriter = new PrintWriter(System.out, true); + + /** The freenet interface. */ + private Freenet7Interface freenetInterface; + + /** The project inserter. */ + private ProjectInserter projectInserter = new ProjectInserter(); + + /** The list of nodes. */ + private Node[] nodes; + + /** The projects. */ + private Project[] projects; + + /** Whether the insert has finished. */ + private boolean finished = false; + + /** Whether the insert finished successfully. */ + private boolean success; + + /** + * Creates a new command-line interface. + * + * @param args + * The command-line arguments + */ + private CLI(String[] args) { + + if ((args.length == 0) || args[0].equals("-h") || args[0].equals("--help")) { + outputWriter.println("\nParameters:\n"); + outputWriter.println(" --config-file="); + outputWriter.println(" --node="); + outputWriter.println(" --project="); + outputWriter.println(" --local-directory="); + outputWriter.println(" --path="); + outputWriter.println(" --edition="); + outputWriter.println("\nA project gets inserted when a new project is loaded on the command line,"); + outputWriter.println("or when the command line is finished. --local-directory, --path, and --edition"); + outputWriter.println("override the parameters in the project."); + return; + } + + String configFile = System.getProperty("user.home") + "/.jSite/config7"; + for (String argument : args) { + String value = argument.substring(argument.indexOf('=') + 1).trim(); + if (argument.startsWith("--config-file=")) { + configFile = value; + } + } + + ConfigurationLocator configurationLocator = new ConfigurationLocator(); + if (configFile != null) { + configurationLocator.setCustomLocation(configFile); + } + Configuration configuration = new Configuration(configurationLocator, configurationLocator.findPreferredLocation()); + + projectInserter.addInsertListener(this); + projects = configuration.getProjects(); + Node node = configuration.getSelectedNode(); + nodes = configuration.getNodes(); + + freenetInterface = new Freenet7Interface(); + freenetInterface.setNode(node); + + projectInserter.setFreenetInterface(freenetInterface); + + Project currentProject = null; + for (String argument : args) { + if (argument.startsWith("--config-file=")) { + /* we already parsed this one. */ + continue; + } + String value = argument.substring(argument.indexOf('=') + 1).trim(); + if (argument.startsWith("--node=")) { + Node newNode = getNode(value); + if (newNode == null) { + outputWriter.println("Node \"" + value + "\" not found."); + return; + } + node = newNode; + freenetInterface.setNode(node); + } else if (argument.startsWith("--project=")) { + if (currentProject != null) { + if (insertProject(currentProject)) { + outputWriter.println("Project \"" + currentProject.getName() + "\" successfully inserted."); + } else { + outputWriter.println("Project \"" + currentProject.getName() + "\" was not successfully inserted."); + } + currentProject = null; + } + currentProject = getProject(value); + if (currentProject == null) { + outputWriter.println("Project \"" + value + "\" not found."); + } + } else if (argument.startsWith("--local-directory")) { + if (currentProject == null) { + outputWriter.println("You can't specifiy --local-directory before --project."); + return; + } + currentProject.setLocalPath(value); + } else if (argument.startsWith("--path=")) { + if (currentProject == null) { + outputWriter.println("You can't specify --path before --project."); + return; + } + currentProject.setPath(value); + } else if (argument.startsWith("--edition=")) { + if (currentProject == null) { + outputWriter.println("You can't specify --edition before --project."); + return; + } + currentProject.setEdition(Integer.parseInt(value)); + } else { + outputWriter.println("Unknown parameter: " + argument); + return; + } + } + + int errorCode = 1; + if (currentProject != null) { + if (insertProject(currentProject)) { + outputWriter.println("Project \"" + currentProject.getName() + "\" successfully inserted."); + errorCode = 0; + } else { + outputWriter.println("Project \"" + currentProject.getName() + "\" was not successfully inserted."); + } + } + + configuration.setProjects(projects); + configuration.save(); + + System.exit(errorCode); + } + + /** + * Returns the project with the given name. + * + * @param name + * The name of the project + * @return The project, or null if no project could be found + */ + private Project getProject(String name) { + for (Project project : projects) { + if (project.getName().equals(name)) { + return project; + } + } + return null; + } + + /** + * Returns the node with the given name. + * + * @param name + * The name of the node + * @return The node, or null if no node could be found + */ + private Node getNode(String name) { + for (Node node : nodes) { + if (node.getName().equals(name)) { + return node; + } + } + return null; + } + + /** + * Inserts the given project. + * + * @param currentProject + * The project to insert + * @return true if the insert finished successfully, + * false otherwise + */ + private boolean insertProject(Project currentProject) { + if (!freenetInterface.hasNode()) { + outputWriter.println("Node is not running!"); + return false; + } + projectInserter.setProject(currentProject); + projectInserter.start(new ProgressListener() { + ++ @Override + public void onProgress(long copied, long length) { + System.out.print("Uploaded: " + copied + " / " + length + " bytes...\r"); + } + }); + synchronized (lockObject) { + while (!finished) { + try { + lockObject.wait(); + } catch (InterruptedException e) { + /* ignore, we're in a loop. */ + } + } + } + return success; + } + + // + // INTERFACE InsertListener + // + + /** + * {@inheritDoc} + */ ++ @Override + public void projectInsertStarted(Project project) { + outputWriter.println("Starting Insert of project \"" + project.getName() + "\"."); + } + + /** + * {@inheritDoc} + */ ++ @Override + public void projectUploadFinished(Project project) { + outputWriter.println("Project \"" + project.getName() + "\" has been uploaded, starting insert..."); + } + + /** + * {@inheritDoc} + */ ++ @Override + public void projectURIGenerated(Project project, String uri) { + outputWriter.println("URI: " + uri); + } + + /** + * {@inheritDoc} + */ ++ @Override + public void projectInsertProgress(Project project, int succeeded, int failed, int fatal, int total, boolean finalized) { + outputWriter.println("Progress: " + succeeded + " done, " + failed + " failed, " + fatal + " fatal, " + total + " total" + (finalized ? " (finalized)" : "") + ", " + ((succeeded + failed + fatal) * 100 / total) + "%"); + } + + /** + * {@inheritDoc} + */ ++ @Override + public void projectInsertFinished(Project project, boolean success, Throwable cause) { + outputWriter.println("Request URI: " + project.getFinalRequestURI(0)); + finished = true; + this.success = success; + synchronized (lockObject) { + lockObject.notify(); + } + } + + // + // MAIN + // + + /** + * Creates a new command-line interface with the given arguments. + * + * @param args + * The command-line arguments + */ + public static void main(String[] args) { + new CLI(args); + } + + } diff --cc src/main/java/de/todesbaum/jsite/main/Main.java index 0000000,38a0f1c..6621931 mode 000000,100644..100644 --- a/src/main/java/de/todesbaum/jsite/main/Main.java +++ b/src/main/java/de/todesbaum/jsite/main/Main.java @@@ -1,0 -1,753 +1,768 @@@ + /* + * jSite - Main.java - Copyright © 2006–2012 David Roden + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + package de.todesbaum.jsite.main; + + import java.awt.Component; + import java.awt.event.ActionEvent; + import java.awt.event.ActionListener; + import java.io.IOException; + import java.text.MessageFormat; + import java.util.Date; + import java.util.HashMap; + import java.util.Locale; + import java.util.Map; + import java.util.logging.ConsoleHandler; + import java.util.logging.Handler; + import java.util.logging.Level; + import java.util.logging.Logger; + + import javax.swing.AbstractAction; + import javax.swing.Action; + import javax.swing.ButtonGroup; + import javax.swing.Icon; + import javax.swing.JList; + import javax.swing.JMenu; + import javax.swing.JMenuBar; + import javax.swing.JMenuItem; + import javax.swing.JOptionPane; + import javax.swing.JPanel; + import javax.swing.JRadioButtonMenuItem; + import javax.swing.event.ListSelectionEvent; + import javax.swing.event.ListSelectionListener; + + import net.pterodactylus.util.image.IconLoader; + import de.todesbaum.jsite.application.Freenet7Interface; + import de.todesbaum.jsite.application.Node; + import de.todesbaum.jsite.application.Project; + import de.todesbaum.jsite.application.ProjectInserter; + import de.todesbaum.jsite.application.ProjectInserter.CheckReport; + import de.todesbaum.jsite.application.ProjectInserter.Issue; + import de.todesbaum.jsite.application.UpdateChecker; + import de.todesbaum.jsite.application.UpdateListener; + import de.todesbaum.jsite.gui.NodeManagerListener; + import de.todesbaum.jsite.gui.NodeManagerPage; + import de.todesbaum.jsite.gui.PreferencesPage; + import de.todesbaum.jsite.gui.ProjectFilesPage; + import de.todesbaum.jsite.gui.ProjectInsertPage; + import de.todesbaum.jsite.gui.ProjectPage; + import de.todesbaum.jsite.i18n.I18n; + import de.todesbaum.jsite.i18n.I18nContainer; + import de.todesbaum.jsite.main.ConfigurationLocator.ConfigurationLocation; + import de.todesbaum.util.swing.TWizard; + import de.todesbaum.util.swing.TWizardPage; + import de.todesbaum.util.swing.WizardListener; + + /** + * The main class that ties together everything. + * + * @author David ‘Bombe’ Roden <bombe@freenetproject.org> + */ + public class Main implements ActionListener, ListSelectionListener, WizardListener, NodeManagerListener, UpdateListener { + + /** The logger. */ + private static final Logger logger = Logger.getLogger(Main.class.getName()); + + /** The version. */ + private static final Version VERSION = new Version(0, 10); + + /** The configuration. */ + private Configuration configuration; + + /** The freenet interface. */ + private Freenet7Interface freenetInterface = new Freenet7Interface(); + + /** The update checker. */ + private final UpdateChecker updateChecker; + + /** The jSite icon. */ + private Icon jSiteIcon; + + /** + * Enumeration for all possible pages. + * + * @author David ‘Bombe’ Roden <bombe@freenetproject.org> + */ + private static enum PageType { + + /** The node manager page. */ + PAGE_NODE_MANAGER, + + /** The project page. */ + PAGE_PROJECTS, + + /** The project files page. */ + PAGE_PROJECT_FILES, + + /** The project insert page. */ + PAGE_INSERT_PROJECT, + + /** The preferences page. */ + PAGE_PREFERENCES + + } + + /** The supported locales. */ + private static final Locale[] SUPPORTED_LOCALES = new Locale[] { Locale.ENGLISH, Locale.GERMAN, Locale.FRENCH }; + + /** The actions that switch the language. */ + private Map languageActions = new HashMap(); + + /** The “manage nodes” action. */ + private Action manageNodeAction; + + /** The “preferences” action. */ + private Action optionsPreferencesAction; + + /** The “check for updates” action. */ + private Action checkForUpdatesAction; + + /** The “about jSite” action. */ + private Action aboutAction; + + /** The wizard. */ + private TWizard wizard; + + /** The node menu. */ + private JMenu nodeMenu; + + /** The currently selected node. */ + private Node selectedNode; + + /** Mapping from page type to page. */ + private final Map pages = new HashMap(); + + /** The original location of the configuration file. */ + private ConfigurationLocation originalLocation; + + /** + * Creates a new core with the default configuration file. + */ + private Main() { + this(null); + } + + /** + * Creates a new core with the given configuration from the given file. + * + * @param configFilename + * The name of the configuration file + */ + private Main(String configFilename) { + /* collect all possible configuration file locations. */ + ConfigurationLocator configurationLocator = new ConfigurationLocator(); + if (configFilename != null) { + configurationLocator.setCustomLocation(configFilename); + } + + originalLocation = configurationLocator.findPreferredLocation(); + logger.log(Level.CONFIG, "Using configuration from " + originalLocation + "."); + configuration = new Configuration(configurationLocator, originalLocation); + + Locale.setDefault(configuration.getLocale()); + I18n.setLocale(configuration.getLocale()); + wizard = new TWizard(); + createActions(); + wizard.setJMenuBar(createMenuBar()); + wizard.setQuitName(I18n.getMessage("jsite.wizard.quit")); + wizard.setPreviousEnabled(false); + wizard.setNextEnabled(true); + wizard.addWizardListener(this); + jSiteIcon = IconLoader.loadIcon("/jsite-icon.png"); + wizard.setIcon(jSiteIcon); + + updateChecker = new UpdateChecker(freenetInterface); + updateChecker.addUpdateListener(this); + updateChecker.start(); + + initPages(); + showPage(PageType.PAGE_PROJECTS); + } + + /** + * Creates all actions. + */ + private void createActions() { + for (final Locale locale : SUPPORTED_LOCALES) { + languageActions.put(locale, new AbstractAction(I18n.getMessage("jsite.menu.language." + locale.getLanguage()), IconLoader.loadIcon("/flag-" + locale.getLanguage() + ".png")) { + ++ @Override + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + switchLanguage(locale); + } + }); + } + manageNodeAction = new AbstractAction(I18n.getMessage("jsite.menu.nodes.manage-nodes")) { + ++ @Override + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + showPage(PageType.PAGE_NODE_MANAGER); + optionsPreferencesAction.setEnabled(true); + wizard.setPreviousName(I18n.getMessage("jsite.wizard.previous")); + wizard.setNextName(I18n.getMessage("jsite.wizard.next")); + } + }; + optionsPreferencesAction = new AbstractAction(I18n.getMessage("jsite.menu.options.preferences")) { + + /** + * {@inheritDoc} + */ ++ @Override + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + optionsPreferences(); + } + }; + checkForUpdatesAction = new AbstractAction(I18n.getMessage("jsite.menu.help.check-for-updates")) { + + /** + * {@inheritDoc} + */ ++ @Override + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent actionEvent) { + showLatestUpdate(); + } + }; + aboutAction = new AbstractAction(I18n.getMessage("jsite.menu.help.about")) { + ++ @Override + @SuppressWarnings("synthetic-access") + public void actionPerformed(ActionEvent e) { + JOptionPane.showMessageDialog(wizard, MessageFormat.format(I18n.getMessage("jsite.about.message"), getVersion().toString()), null, JOptionPane.INFORMATION_MESSAGE, jSiteIcon); + } + }; + + I18nContainer.getInstance().registerRunnable(new Runnable() { + ++ @Override + @SuppressWarnings("synthetic-access") + public void run() { + manageNodeAction.putValue(Action.NAME, I18n.getMessage("jsite.menu.nodes.manage-nodes")); + optionsPreferencesAction.putValue(Action.NAME, I18n.getMessage("jsite.menu.options.preferences")); + checkForUpdatesAction.putValue(Action.NAME, I18n.getMessage("jsite.menu.help.check-for-updates")); + aboutAction.putValue(Action.NAME, I18n.getMessage("jsite.menu.help.about")); + } + }); + } + + /** + * Creates the menu bar. + * + * @return The menu bar + */ + private JMenuBar createMenuBar() { + JMenuBar menuBar = new JMenuBar(); + final JMenu languageMenu = new JMenu(I18n.getMessage("jsite.menu.languages")); + menuBar.add(languageMenu); + ButtonGroup languageButtonGroup = new ButtonGroup(); + for (Locale locale : SUPPORTED_LOCALES) { + Action languageAction = languageActions.get(locale); + JRadioButtonMenuItem menuItem = new JRadioButtonMenuItem(languageActions.get(locale)); + if (locale.equals(Locale.getDefault())) { + menuItem.setSelected(true); + } + languageAction.putValue("menuItem", menuItem); + languageButtonGroup.add(menuItem); + languageMenu.add(menuItem); + } + nodeMenu = new JMenu(I18n.getMessage("jsite.menu.nodes")); + menuBar.add(nodeMenu); + selectedNode = configuration.getSelectedNode(); + nodesUpdated(configuration.getNodes()); + + final JMenu optionsMenu = new JMenu(I18n.getMessage("jsite.menu.options")); + menuBar.add(optionsMenu); + optionsMenu.add(optionsPreferencesAction); + + /* evil hack to right-align the help menu */ + JPanel panel = new JPanel(); + panel.setOpaque(false); + menuBar.add(panel); + + final JMenu helpMenu = new JMenu(I18n.getMessage("jsite.menu.help")); + menuBar.add(helpMenu); + helpMenu.add(checkForUpdatesAction); + helpMenu.add(aboutAction); + + I18nContainer.getInstance().registerRunnable(new Runnable() { + ++ @Override + @SuppressWarnings("synthetic-access") + public void run() { + languageMenu.setText(I18n.getMessage("jsite.menu.languages")); + nodeMenu.setText(I18n.getMessage("jsite.menu.nodes")); + optionsMenu.setText(I18n.getMessage("jsite.menu.options")); + helpMenu.setText(I18n.getMessage("jsite.menu.help")); + for (Map.Entry languageActionEntry : languageActions.entrySet()) { + languageActionEntry.getValue().putValue(Action.NAME, I18n.getMessage("jsite.menu.language." + languageActionEntry.getKey().getLanguage())); + } + } + }); + + return menuBar; + } + + /** + * Initializes all pages. + */ + private void initPages() { + NodeManagerPage nodeManagerPage = new NodeManagerPage(wizard); + nodeManagerPage.setName("page.node-manager"); + nodeManagerPage.addNodeManagerListener(this); + nodeManagerPage.setNodes(configuration.getNodes()); + pages.put(PageType.PAGE_NODE_MANAGER, nodeManagerPage); + + ProjectPage projectPage = new ProjectPage(wizard); + projectPage.setName("page.project"); + projectPage.setProjects(configuration.getProjects()); + projectPage.setFreenetInterface(freenetInterface); + projectPage.addListSelectionListener(this); + pages.put(PageType.PAGE_PROJECTS, projectPage); + + ProjectFilesPage projectFilesPage = new ProjectFilesPage(wizard); + projectFilesPage.setName("page.project.files"); + pages.put(PageType.PAGE_PROJECT_FILES, projectFilesPage); + + ProjectInsertPage projectInsertPage = new ProjectInsertPage(wizard); + projectInsertPage.setName("page.project.insert"); + projectInsertPage.setFreenetInterface(freenetInterface); + pages.put(PageType.PAGE_INSERT_PROJECT, projectInsertPage); + + PreferencesPage preferencesPage = new PreferencesPage(wizard); + preferencesPage.setName("page.preferences"); + preferencesPage.setTempDirectory(configuration.getTempDirectory()); + pages.put(PageType.PAGE_PREFERENCES, preferencesPage); + } + + /** + * Shows the page with the given type. + * + * @param pageType + * The page type to show + */ + private void showPage(PageType pageType) { + wizard.setPreviousEnabled(pageType.ordinal() > 0); + wizard.setNextEnabled(pageType.ordinal() < (pages.size() - 1)); + wizard.setPage(pages.get(pageType)); + wizard.setTitle(pages.get(pageType).getHeading() + " - jSite"); + } + + /** + * Returns whether a configuration file would be overwritten when calling + * {@link #saveConfiguration()}. + * + * @return {@code true} if {@link #saveConfiguration()} would overwrite an + * existing file, {@code false} otherwise + */ + private boolean isOverwritingConfiguration() { + return configuration.getConfigurationLocator().hasFile(configuration.getConfigurationDirectory()); + } + + /** + * Saves the configuration. + * + * @return true if the configuration could be saved, + * false otherwise + */ + private boolean saveConfiguration() { + NodeManagerPage nodeManagerPage = (NodeManagerPage) pages.get(PageType.PAGE_NODE_MANAGER); + configuration.setNodes(nodeManagerPage.getNodes()); + if (selectedNode != null) { + configuration.setSelectedNode(selectedNode); + } + + ProjectPage projectPage = (ProjectPage) pages.get(PageType.PAGE_PROJECTS); + configuration.setProjects(projectPage.getProjects()); + + PreferencesPage preferencesPage = (PreferencesPage) pages.get(PageType.PAGE_PREFERENCES); + configuration.setTempDirectory(preferencesPage.getTempDirectory()); + + return configuration.save(); + } + + /** + * Finds a supported locale for the given locale. + * + * @param forLocale + * The locale to find a supported locale for + * @return The supported locale that was found, or the default locale if no + * supported locale could be found + */ - private Locale findSupportedLocale(Locale forLocale) { ++ private static Locale findSupportedLocale(Locale forLocale) { + for (Locale locale : SUPPORTED_LOCALES) { + if (locale.equals(forLocale)) { + return locale; + } + } + for (Locale locale : SUPPORTED_LOCALES) { + if (locale.getCountry().equals(forLocale.getCountry()) && locale.getLanguage().equals(forLocale.getLanguage())) { + return locale; + } + } + for (Locale locale : SUPPORTED_LOCALES) { + if (locale.getLanguage().equals(forLocale.getLanguage())) { + return locale; + } + } + return SUPPORTED_LOCALES[0]; + } + + /** + * Returns the version. + * + * @return The version + */ + public static final Version getVersion() { + return VERSION; + } + + // + // ACTIONS + // + + /** + * Switches the language of the interface to the given locale. + * + * @param locale + * The locale to switch to + */ + private void switchLanguage(Locale locale) { + Locale supportedLocale = findSupportedLocale(locale); + Action languageAction = languageActions.get(supportedLocale); + JRadioButtonMenuItem menuItem = (JRadioButtonMenuItem) languageAction.getValue("menuItem"); + menuItem.setSelected(true); + I18n.setLocale(supportedLocale); + for (Runnable i18nRunnable : I18nContainer.getInstance()) { + try { + i18nRunnable.run(); + } catch (Throwable t) { + /* we probably shouldn't swallow this. */ + } + } + wizard.setPage(wizard.getPage()); + configuration.setLocale(supportedLocale); + } + + /** + * Shows a dialog with general preferences. + */ + private void optionsPreferences() { + ((PreferencesPage) pages.get(PageType.PAGE_PREFERENCES)).setConfigurationLocation(configuration.getConfigurationDirectory()); + ((PreferencesPage) pages.get(PageType.PAGE_PREFERENCES)).setHasNextToJarConfiguration(configuration.getConfigurationLocator().isValidLocation(ConfigurationLocation.NEXT_TO_JAR_FILE)); + ((PreferencesPage) pages.get(PageType.PAGE_PREFERENCES)).setHasCustomConfiguration(configuration.getConfigurationLocator().isValidLocation(ConfigurationLocation.CUSTOM)); + ((PreferencesPage) pages.get(PageType.PAGE_PREFERENCES)).setUseEarlyEncode(configuration.useEarlyEncode()); + ((PreferencesPage) pages.get(PageType.PAGE_PREFERENCES)).setPriority(configuration.getPriority()); + ((PreferencesPage) pages.get(PageType.PAGE_PREFERENCES)).setManifestPutter(configuration.getManifestPutter()); + showPage(PageType.PAGE_PREFERENCES); + optionsPreferencesAction.setEnabled(false); + wizard.setNextEnabled(true); + wizard.setNextName(I18n.getMessage("jsite.wizard.next")); + } + + /** + * Shows a dialog box that shows the last version that was found by the + * {@link UpdateChecker}. + */ + private void showLatestUpdate() { + Version latestVersion = updateChecker.getLatestVersion(); + int versionDifference = latestVersion.compareTo(VERSION); + if (versionDifference > 0) { + JOptionPane.showMessageDialog(wizard, MessageFormat.format(I18n.getMessage("jsite.update-checker.latest-version.newer.message"), VERSION, latestVersion), I18n.getMessage("jsite.update-checker.latest-version.title"), JOptionPane.INFORMATION_MESSAGE); + } else if (versionDifference < 0) { + JOptionPane.showMessageDialog(wizard, MessageFormat.format(I18n.getMessage("jsite.update-checker.latest-version.older.message"), VERSION, latestVersion), I18n.getMessage("jsite.update-checker.latest-version.title"), JOptionPane.INFORMATION_MESSAGE); + } else { + JOptionPane.showMessageDialog(wizard, MessageFormat.format(I18n.getMessage("jsite.update-checker.latest-version.okay.message"), VERSION, latestVersion), I18n.getMessage("jsite.update-checker.latest-version.title"), JOptionPane.INFORMATION_MESSAGE); + } + } + + // + // INTERFACE ListSelectionListener + // + + /** + * {@inheritDoc} + */ ++ @Override + public void valueChanged(ListSelectionEvent e) { + JList list = (JList) e.getSource(); + int selectedRow = list.getSelectedIndex(); + wizard.setNextEnabled(selectedRow > -1); + } + + // + // INTERFACE WizardListener + // + + /** + * {@inheritDoc} + */ ++ @Override + public void wizardNextPressed(TWizard wizard) { + String pageName = wizard.getPage().getName(); + if ("page.node-manager".equals(pageName)) { + showPage(PageType.PAGE_PROJECTS); + } else if ("page.project".equals(pageName)) { + ProjectPage projectPage = (ProjectPage) wizard.getPage(); + Project project = projectPage.getSelectedProject(); + if ((project.getLocalPath() == null) || (project.getLocalPath().trim().length() == 0)) { + JOptionPane.showMessageDialog(wizard, I18n.getMessage("jsite.warning.no-local-path"), null, JOptionPane.ERROR_MESSAGE); + return; + } + if ((project.getPath() == null) || (project.getPath().trim().length() == 0)) { + JOptionPane.showMessageDialog(wizard, I18n.getMessage("jsite.warning.no-path"), null, JOptionPane.ERROR_MESSAGE); + return; + } + ((ProjectFilesPage) pages.get(PageType.PAGE_PROJECT_FILES)).setProject(project); + ((ProjectInsertPage) pages.get(PageType.PAGE_INSERT_PROJECT)).setProject(project); + showPage(PageType.PAGE_PROJECT_FILES); + } else if ("page.project.files".equals(pageName)) { + ProjectPage projectPage = (ProjectPage) pages.get(PageType.PAGE_PROJECTS); + Project project = projectPage.getSelectedProject(); + if (selectedNode == null) { + JOptionPane.showMessageDialog(wizard, I18n.getMessage("jsite.error.no-node-selected"), null, JOptionPane.ERROR_MESSAGE); + return; + } + CheckReport checkReport = ProjectInserter.validateProject(project); + for (Issue issue : checkReport) { + if (issue.isFatal()) { + JOptionPane.showMessageDialog(wizard, MessageFormat.format(I18n.getMessage("jsite." + issue.getErrorKey()), (Object[]) issue.getParameters()), null, JOptionPane.ERROR_MESSAGE); + return; + } + if (JOptionPane.showConfirmDialog(wizard, MessageFormat.format(I18n.getMessage("jsite." + issue.getErrorKey()), (Object[]) issue.getParameters()), null, JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE) != JOptionPane.OK_OPTION) { + return; + } + } + boolean nodeRunning = false; + try { + nodeRunning = freenetInterface.isNodePresent(); + } catch (IOException e) { + /* ignore. */ + } + if (!nodeRunning) { + JOptionPane.showMessageDialog(wizard, I18n.getMessage("jsite.error.no-node-running"), null, JOptionPane.ERROR_MESSAGE); + return; + } + configuration.save(); + showPage(PageType.PAGE_INSERT_PROJECT); + ProjectInsertPage projectInsertPage = (ProjectInsertPage) pages.get(PageType.PAGE_INSERT_PROJECT); + String tempDirectory = ((PreferencesPage) pages.get(PageType.PAGE_PREFERENCES)).getTempDirectory(); + projectInsertPage.setTempDirectory(tempDirectory); + projectInsertPage.setUseEarlyEncode(configuration.useEarlyEncode()); + projectInsertPage.setPriority(configuration.getPriority()); + projectInsertPage.setManifestPutter(configuration.getManifestPutter()); + projectInsertPage.startInsert(); + nodeMenu.setEnabled(false); + optionsPreferencesAction.setEnabled(false); + } else if ("page.project.insert".equals(pageName)) { + ProjectInsertPage projectInsertPage = (ProjectInsertPage) pages.get(PageType.PAGE_INSERT_PROJECT); + if (projectInsertPage.isRunning()) { + projectInsertPage.stopInsert(); + } else { + showPage(PageType.PAGE_PROJECTS); + nodeMenu.setEnabled(true); + optionsPreferencesAction.setEnabled(true); + } + } else if ("page.preferences".equals(pageName)) { + PreferencesPage preferencesPage = (PreferencesPage) pages.get(PageType.PAGE_PREFERENCES); + showPage(PageType.PAGE_PROJECTS); + optionsPreferencesAction.setEnabled(true); + configuration.setUseEarlyEncode(preferencesPage.useEarlyEncode()); + configuration.setPriority(preferencesPage.getPriority()); + configuration.setManifestPutter(preferencesPage.getManifestPutter()); + configuration.setConfigurationLocation(preferencesPage.getConfigurationLocation()); + } + } + + /** + * {@inheritDoc} + */ ++ @Override + public void wizardPreviousPressed(TWizard wizard) { + String pageName = wizard.getPage().getName(); + if ("page.project".equals(pageName) || "page.preferences".equals(pageName)) { + showPage(PageType.PAGE_NODE_MANAGER); + optionsPreferencesAction.setEnabled(true); + } else if ("page.project.files".equals(pageName)) { + showPage(PageType.PAGE_PROJECTS); + } else if ("page.project.insert".equals(pageName)) { + showPage(PageType.PAGE_PROJECT_FILES); + } + } + + /** + * {@inheritDoc} + */ ++ @Override + public void wizardQuitPressed(TWizard wizard) { + if (((ProjectPage) pages.get(PageType.PAGE_PROJECTS)).wasUriCopied() || ((ProjectInsertPage) pages.get(PageType.PAGE_INSERT_PROJECT)).wasUriCopied()) { + JOptionPane.showMessageDialog(wizard, I18n.getMessage("jsite.project.warning.use-clipboard-now")); + } + if (JOptionPane.showConfirmDialog(wizard, I18n.getMessage("jsite.quit.question"), I18n.getMessage("jsite.quit.question.title"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE) == JOptionPane.OK_OPTION) { + if (isOverwritingConfiguration() && !originalLocation.equals(configuration.getConfigurationDirectory())) { + int overwriteConfigurationAnswer = JOptionPane.showConfirmDialog(wizard, MessageFormat.format(I18n.getMessage("jsite.quit.overwrite-configuration"), configuration.getConfigurationLocator().getFile(configuration.getConfigurationDirectory())), I18n.getMessage("jsite.quit.overwrite-configuration.title"), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE); + if (overwriteConfigurationAnswer == JOptionPane.YES_OPTION) { + if (saveConfiguration()) { + System.exit(0); + } + } else if (overwriteConfigurationAnswer == JOptionPane.CANCEL_OPTION) { + return; + } + if (overwriteConfigurationAnswer == JOptionPane.NO_OPTION) { + System.exit(0); + } + } else { + if (saveConfiguration()) { + System.exit(0); + } + } + if (JOptionPane.showConfirmDialog(wizard, I18n.getMessage("jsite.quit.config-not-saved"), null, JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE) == JOptionPane.OK_OPTION) { + System.exit(0); + } + } + } + + // + // INTERFACE NodeManagerListener + // + + /** + * {@inheritDoc} + */ ++ @Override + public void nodesUpdated(Node[] nodes) { + nodeMenu.removeAll(); + ButtonGroup nodeButtonGroup = new ButtonGroup(); + Node newSelectedNode = null; + for (Node node : nodes) { + JRadioButtonMenuItem nodeMenuItem = new JRadioButtonMenuItem(node.getName()); + nodeMenuItem.putClientProperty("Node", node); + nodeMenuItem.addActionListener(this); + nodeButtonGroup.add(nodeMenuItem); + if (node.equals(selectedNode)) { + newSelectedNode = node; + nodeMenuItem.setSelected(true); + } + nodeMenu.add(nodeMenuItem); + } + nodeMenu.addSeparator(); + nodeMenu.add(manageNodeAction); + selectedNode = newSelectedNode; + freenetInterface.setNode(selectedNode); + } + + /** + * {@inheritDoc} + */ ++ @Override + public void nodeSelected(Node node) { + for (Component menuItem : nodeMenu.getMenuComponents()) { + if (menuItem instanceof JMenuItem) { + if (node.equals(((JMenuItem) menuItem).getClientProperty("Node"))) { + ((JMenuItem) menuItem).setSelected(true); + } + } + } + freenetInterface.setNode(node); + selectedNode = node; + } + + // + // INTERFACE ActionListener + // + + /** + * {@inheritDoc} + */ ++ @Override + public void actionPerformed(ActionEvent e) { + Object source = e.getSource(); + if (source instanceof JRadioButtonMenuItem) { + JRadioButtonMenuItem menuItem = (JRadioButtonMenuItem) source; + Node node = (Node) menuItem.getClientProperty("Node"); + selectedNode = node; + freenetInterface.setNode(selectedNode); + } + } + + // + // INTERFACE UpdateListener + // + + /** + * {@inheritDoc} + */ ++ @Override + public void foundUpdateData(Version foundVersion, long versionTimestamp) { + logger.log(Level.FINEST, "Found version {0} from {1,date}.", new Object[] { foundVersion, versionTimestamp }); + if (foundVersion.compareTo(VERSION) > 0) { + JOptionPane.showMessageDialog(wizard, MessageFormat.format(I18n.getMessage("jsite.update-checker.found-version.message"), foundVersion.toString(), new Date(versionTimestamp)), I18n.getMessage("jsite.update-checker.found-version.title"), JOptionPane.INFORMATION_MESSAGE); + } + } + + // + // MAIN METHOD + // + + /** + * Main method that is called by the VM. + * + * @param args + * The command-line arguments + */ + public static void main(String[] args) { + /* initialize logger. */ + Logger logger = Logger.getLogger("de.todesbaum"); + Handler handler = new ConsoleHandler(); + logger.addHandler(handler); + String configFilename = null; + boolean nextIsConfigFilename = false; + for (String argument : args) { + if (nextIsConfigFilename) { + configFilename = argument; + nextIsConfigFilename = false; + } + if ("--help".equals(argument)) { + printHelp(); + return; + } else if ("--debug".equals(argument)) { + logger.setLevel(Level.ALL); + handler.setLevel(Level.ALL); + } else if ("--config-file".equals(argument)) { + nextIsConfigFilename = true; + } + } + if (nextIsConfigFilename) { + System.out.println("--config-file needs parameter!"); + return; + } + new Main(configFilename); + } + + /** + * Prints a small syntax help. + */ + private static void printHelp() { + System.out.println("--help\tshows this cruft"); + System.out.println("--debug\tenables some debug output"); + System.out.println("--config-file \tuse specified configuration file"); + } + + } diff --cc src/main/java/de/todesbaum/jsite/main/Version.java index 0000000,977836c..373703c mode 000000,100644..100644 --- a/src/main/java/de/todesbaum/jsite/main/Version.java +++ b/src/main/java/de/todesbaum/jsite/main/Version.java @@@ -1,0 -1,113 +1,114 @@@ + /* + * jSite - Version.java - Copyright © 2006–2012 David Roden + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + package de.todesbaum.jsite.main; + + /** + * Container for version information. + * + * @author David ‘Bombe’ Roden <bombe@freenetproject.org> + */ + public class Version implements Comparable { + + /** The components of the version information. */ + private final int[] components; + + /** + * Creates a new version container with the given components. + * + * @param components + * The version components + */ + public Version(int... components) { + this.components = new int[components.length]; + System.arraycopy(components, 0, this.components, 0, components.length); + } + + /** + * Returns the number of version components. + * + * @return The number of version components + */ + public int size() { + return components.length; + } + + /** + * Returns the version component with the given index. + * + * @param index + * The index of the version component + * @return The version component + */ + public int getComponent(int index) { + return components[index]; + } + + /** + * Parses a version from the given string. + * + * @param versionString + * The version string to parse + * @return The parsed version, or null if the string could not + * be parsed + */ + public static Version parse(String versionString) { + String[] componentStrings = versionString.split("\\."); + int[] components = new int[componentStrings.length]; + int index = -1; + for (String componentString : componentStrings) { + try { + components[++index] = Integer.parseInt(componentString); + } catch (NumberFormatException nfe1) { + return null; + } + } + return new Version(components); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + StringBuilder versionString = new StringBuilder(); + for (int component : components) { + if (versionString.length() != 0) { + versionString.append('.'); + } + versionString.append(component); + } + return versionString.toString(); + } + + /** + * {@inheritDoc} + */ ++ @Override + public int compareTo(Version version) { + int lessComponents = Math.min(components.length, version.components.length); + for (int index = 0; index < lessComponents; index++) { + if (version.components[index] == components[index]) { + continue; + } + return components[index] - version.components[index]; + } + return components.length - version.components.length; + } + + }