Merge branch 'release-0.10' 0.10
authorDavid ‘Bombe’ Roden <bombe@freenetproject.org>
Sun, 1 Apr 2012 16:30:06 +0000 (18:30 +0200)
committerDavid ‘Bombe’ Roden <bombe@freenetproject.org>
Sun, 1 Apr 2012 16:30:06 +0000 (18:30 +0200)
64 files changed:
.gitignore
build.xml
src/de/todesbaum/jsite/application/AbortedException.java
src/de/todesbaum/jsite/application/FileOption.java
src/de/todesbaum/jsite/application/Freenet7Interface.java
src/de/todesbaum/jsite/application/InsertListener.java
src/de/todesbaum/jsite/application/KeyDialog.java
src/de/todesbaum/jsite/application/Node.java
src/de/todesbaum/jsite/application/Project.java
src/de/todesbaum/jsite/application/ProjectInserter.java
src/de/todesbaum/jsite/application/UpdateChecker.java
src/de/todesbaum/jsite/application/UpdateListener.java
src/de/todesbaum/jsite/gui/FileScanner.java
src/de/todesbaum/jsite/gui/FileScannerListener.java
src/de/todesbaum/jsite/gui/NodeManagerListener.java
src/de/todesbaum/jsite/gui/NodeManagerPage.java
src/de/todesbaum/jsite/gui/PreferencesPage.java
src/de/todesbaum/jsite/gui/ProjectFilesPage.java
src/de/todesbaum/jsite/gui/ProjectInsertPage.java
src/de/todesbaum/jsite/gui/ProjectPage.java
src/de/todesbaum/jsite/i18n/I18n.java
src/de/todesbaum/jsite/i18n/I18nContainer.java
src/de/todesbaum/jsite/i18n/jSite.properties
src/de/todesbaum/jsite/i18n/jSite_de.properties
src/de/todesbaum/jsite/i18n/jSite_fr.properties
src/de/todesbaum/jsite/main/CLI.java
src/de/todesbaum/jsite/main/Configuration.java
src/de/todesbaum/jsite/main/ConfigurationLocator.java [new file with mode: 0644]
src/de/todesbaum/jsite/main/Main.java
src/de/todesbaum/jsite/main/Version.java
src/de/todesbaum/util/freenet/fcp2/Client.java
src/de/todesbaum/util/freenet/fcp2/ClientGet.java
src/de/todesbaum/util/freenet/fcp2/ClientHello.java
src/de/todesbaum/util/freenet/fcp2/ClientPut.java
src/de/todesbaum/util/freenet/fcp2/ClientPutComplexDir.java
src/de/todesbaum/util/freenet/fcp2/ClientPutDir.java
src/de/todesbaum/util/freenet/fcp2/Command.java
src/de/todesbaum/util/freenet/fcp2/Connection.java
src/de/todesbaum/util/freenet/fcp2/ConnectionListener.java
src/de/todesbaum/util/freenet/fcp2/DirectFileEntry.java
src/de/todesbaum/util/freenet/fcp2/DiskFileEntry.java
src/de/todesbaum/util/freenet/fcp2/FileEntry.java
src/de/todesbaum/util/freenet/fcp2/GenerateSSK.java
src/de/todesbaum/util/freenet/fcp2/Message.java
src/de/todesbaum/util/freenet/fcp2/Node.java
src/de/todesbaum/util/freenet/fcp2/Persistence.java
src/de/todesbaum/util/freenet/fcp2/PriorityClass.java
src/de/todesbaum/util/freenet/fcp2/RedirectFileEntry.java
src/de/todesbaum/util/freenet/fcp2/ReturnType.java
src/de/todesbaum/util/freenet/fcp2/Verbosity.java
src/de/todesbaum/util/image/IconLoader.java
src/de/todesbaum/util/io/Closer.java
src/de/todesbaum/util/io/LineInputStream.java
src/de/todesbaum/util/io/ReplacingOutputStream.java [deleted file]
src/de/todesbaum/util/io/StreamCopier.java
src/de/todesbaum/util/io/TeeOutputStream.java [new file with mode: 0644]
src/de/todesbaum/util/io/TempFileInputStream.java
src/de/todesbaum/util/swing/SortedListModel.java
src/de/todesbaum/util/swing/TLabel.java
src/de/todesbaum/util/swing/TWizard.java
src/de/todesbaum/util/swing/TWizardPage.java
src/de/todesbaum/util/swing/WizardListener.java
src/de/todesbaum/util/xml/SimpleXML.java
src/de/todesbaum/util/xml/XML.java

index ba077a4..7dbcc35 100644 (file)
@@ -1 +1,3 @@
 bin
+build/
+dist/
index 9725f07..d997bd1 100644 (file)
--- a/build.xml
+++ b/build.xml
        </target>
 
        <target name="compile-maintainer" depends="prepare" if="maintainer-build">
-               <javac destdir="${bin.dir}" debug="false" optimize="true">
+               <javac destdir="${bin.dir}" debug="${javac.debug}" optimize="true">
                        <src path="${src.dir}" />
                        <sourcepath path="${todesbaum.src}" />
                </javac>
        </target>
        
        <target name="compile-non-maintainer" depends="prepare" unless="maintainer-build">
-               <javac destdir="${bin.dir}" debug="false" optimize="true">
+               <javac destdir="${bin.dir}" debug="${javac.debug}" optimize="true">
                        <src path="${src.dir}" />
                        <sourcepath path="${todesbaum.src}" />
                </javac>
index bde6953..0dfd8c7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * jSite - AbortedException.java - Copyright © 2010 David Roden
+ * jSite - AbortedException.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
index 31e6698..c8824de 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * jSite - a tool for uploading websites into Freenet Copyright (C) 2006 David
- * Roden
+ * jSite - FileOption.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
@@ -38,21 +37,27 @@ public class FileOption {
        /** The default changed name. */
        private static final String DEFAULT_CHANGED_NAME = null;
 
-       /** The default container. */
-       private static final String DEFAULT_CONTAINER = "";
-
-       /** The default edition range. */
-       private static final int DEFAULT_EDITION_RANGE = 3;
-
-       /** The default for the replace edition state. */
-       private static final boolean DEFAULT_REPLACE_EDITION = false;
-
        /** The insert state. */
        private boolean insert;
 
+       /** Whether to force an insert. */
+       private boolean forceInsert;
+
        /** Whether to insert a redirect. */
        private boolean insertRedirect;
 
+       /** The hash of the last insert. */
+       private String lastInsertHash;
+
+       /** The edition of the last insert. */
+       private int lastInsertEdition;
+
+       /** The filename of the last insert. */
+       private String lastInsertFilename;
+
+       /** The current hash of the file. */
+       private String currentHash;
+
        /** The custom key. */
        private String customKey;
 
@@ -65,15 +70,6 @@ public class FileOption {
        /** The current MIME type. */
        private String mimeType;
 
-       /** The container. */
-       private String container;
-
-       /** The edition range. */
-       private int editionRange;
-
-       /** The replace edition state. */
-       private boolean replaceEdition;
-
        /**
         * Creates new file options.
         *
@@ -87,9 +83,6 @@ public class FileOption {
                changedName = DEFAULT_CHANGED_NAME;
                this.defaultMimeType = defaultMimeType;
                mimeType = defaultMimeType;
-               container = DEFAULT_CONTAINER;
-               editionRange = DEFAULT_EDITION_RANGE;
-               replaceEdition = DEFAULT_REPLACE_EDITION;
        }
 
        /**
@@ -145,6 +138,31 @@ public class FileOption {
        }
 
        /**
+        * Returns whether the insert of this file should be forced, even if its
+        * current hash matches the last insert hash.
+        *
+        * @return {@code true} to force the insert of this file, {@code false}
+        *         otherwise
+        */
+       public boolean isForceInsert() {
+               return forceInsert;
+       }
+
+       /**
+        * Sets whether to force the insert of this file, even if its current hash
+        * matches the last insert hash.
+        *
+        * @param forceInsert
+        *            {@code true} to force the insert of this file, {@code false}
+        *            otherwise
+        * @return These file options
+        */
+       public FileOption setForceInsert(boolean forceInsert) {
+               this.forceInsert = forceInsert;
+               return this;
+       }
+
+       /**
         * Returns whether a redirect to a different key should be inserted. This
         * will only matter if {@link #isInsert()} returns {@code false}. The key
         * that should be redirected to still needs to be specified via
@@ -173,6 +191,93 @@ public class FileOption {
        }
 
        /**
+        * Returns the hash of the file when it was last inserted
+        *
+        * @return The last hash of the file
+        */
+       public String getLastInsertHash() {
+               return lastInsertHash;
+       }
+
+       /**
+        * Sets the hash of the file when it was last inserted.
+        *
+        * @param lastInsertHash
+        *            The last hash of the file
+        * @return These file options
+        */
+       public FileOption setLastInsertHash(String lastInsertHash) {
+               this.lastInsertHash = lastInsertHash;
+               return this;
+       }
+
+       /**
+        * Returns the last edition at which this file was inserted.
+        *
+        * @return The last insert edition of this file
+        */
+       public int getLastInsertEdition() {
+               return lastInsertEdition;
+       }
+
+       /**
+        * Sets the last insert edition of this file.
+        *
+        * @param lastInsertEdition
+        *            The last insert edition of this file
+        * @return These file options
+        */
+       public FileOption setLastInsertEdition(int lastInsertEdition) {
+               this.lastInsertEdition = lastInsertEdition;
+               return this;
+       }
+
+       /**
+        * Returns the name of the file when it was last inserted.
+        *
+        * @return The name of the file at the last insert
+        */
+       public String getLastInsertFilename() {
+               return lastInsertFilename;
+       }
+
+       /**
+        * Sets the name of the file when it was last inserted.
+        *
+        * @param lastInsertFilename
+        *            The name of the file at the last insert.
+        * @return These file options
+        */
+       public FileOption setLastInsertFilename(String lastInsertFilename) {
+               this.lastInsertFilename = lastInsertFilename;
+               return this;
+       }
+
+       /**
+        * Returns the current hash of the file. This value is ony a temporary value
+        * that is copied to {@link #getLastInsertHash()} when a project has
+        * finished inserting.
+        *
+        * @see Project#onSuccessfulInsert()
+        * @return The current hash of the file
+        */
+       public String getCurrentHash() {
+               return currentHash;
+       }
+
+       /**
+        * Sets the current hash of the file.
+        *
+        * @param currentHash
+        *            The current hash of the file
+        * @return These file options
+        */
+       public FileOption setCurrentHash(String currentHash) {
+               this.currentHash = currentHash;
+               return this;
+       }
+
+       /**
         * Returns whether this file has a changed name. Use
         * {@link #getChangedName()} is this method returns {@code true}.
         *
@@ -231,69 +336,6 @@ public class FileOption {
        }
 
        /**
-        * Returns the name of the container this file should be put in.
-        *
-        * @return The name of the container
-        */
-       public String getContainer() {
-               return container;
-       }
-
-       /**
-        * Sets the name of the container this file should be put in.
-        *
-        * @param container
-        *            The name of the container
-        */
-       public void setContainer(String container) {
-               if (container == null) {
-                       this.container = DEFAULT_CONTAINER;
-               } else {
-                       this.container = container;
-               }
-       }
-
-       /**
-        * Sets whether the file should have “$[EDITION+<i>n</i>]” tags replaced.
-        *
-        * @param replaceEdition
-        *            <code>true</code> to replace tags, <code>false</code> not to
-        *            replace
-        */
-       public void setReplaceEdition(boolean replaceEdition) {
-               this.replaceEdition = replaceEdition;
-       }
-
-       /**
-        * Returns whether the file should have “$[EDITION+<i>n</i>]” tags replaced.
-        *
-        * @return <code>true</code> if tags should be replaced, <code>false</code>
-        *         otherwise
-        */
-       public boolean getReplaceEdition() {
-               return replaceEdition;
-       }
-
-       /**
-        * Sets the range of editions that should be replaced.
-        *
-        * @param editionRange
-        *            The range editions to replace
-        */
-       public void setEditionRange(int editionRange) {
-               this.editionRange = editionRange;
-       }
-
-       /**
-        * Returns the range of editions that should be replaced.
-        *
-        * @return The range of editions to replace
-        */
-       public int getEditionRange() {
-               return editionRange;
-       }
-
-       /**
         * Returns whether the options for this file have been modified, i.e. are
         * not at their default values.
         *
@@ -313,15 +355,6 @@ public class FileOption {
                if (!defaultMimeType.equals(mimeType)) {
                        return true;
                }
-               if (!DEFAULT_CONTAINER.equals(container)) {
-                       return true;
-               }
-               if (replaceEdition != DEFAULT_REPLACE_EDITION) {
-                       return true;
-               }
-               if (editionRange != DEFAULT_EDITION_RANGE) {
-                       return true;
-               }
                if (insertRedirect != DEFAULT_INSERT_REDIRECT) {
                        return true;
                }
index a271f6b..78250b2 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * jSite -
- * Copyright (C) 2006 David Roden
+ * jSite - Freenet7Interface.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
@@ -142,7 +141,7 @@ public class Freenet7Interface {
         */
        public String[] generateKeyPair() throws IOException {
                if (!isNodePresent()) {
-                       return null;
+                       throw new IOException("Node is offline.");
                }
                GenerateSSK generateSSK = new GenerateSSK();
                Client client = new Client(connection, generateSSK);
index 40cdaf1..9b6b332 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * jSite - a tool for uploading websites into Freenet
- * Copyright (C) 2006 David Roden
+ * jSite - InsertListener.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
index 536f8c6..4b64f72 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * jSite - KeyDialog.java - Copyright © 2010 David Roden
+ * 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
index aae1f7a..df7492f 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * jSite-0.7 -
- * Copyright (C) 2006 David Roden
+ * jSite - Node.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
index 669b782..fa0b774 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * jSite - a tool for uploading websites into Freenet Copyright (C) 2006 David
- * Roden
+ * 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
@@ -23,6 +22,7 @@ import java.io.File;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Map.Entry;
 
 import de.todesbaum.util.mime.DefaultMIMETypes;
 
@@ -420,4 +420,22 @@ public class Project implements Comparable<Project> {
                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<String, FileOption> 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);
+               }
+       }
+
 }
index 756e121..3a49c49 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * jSite - a tool for uploading websites into Freenet
- * Copyright (C) 2006 David Roden
+ * 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
 
 package de.todesbaum.jsite.application;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.Map.Entry;
+import java.util.Set;
 import java.util.logging.Level;
 import java.util.logging.Logger;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
 
 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;
-import de.todesbaum.util.io.Closer;
-import de.todesbaum.util.io.ReplacingOutputStream;
-import de.todesbaum.util.io.StreamCopier;
+import de.todesbaum.util.io.StreamCopier.ProgressListener;
 
 /**
  * Manages project inserts.
@@ -94,6 +88,18 @@ public class ProjectInserter implements FileScannerListener, Runnable {
        /** 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.
         *
@@ -220,10 +226,45 @@ public class ProjectInserter implements FileScannerListener, Runnable {
        }
 
        /**
+        * 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() {
+       public void start(ProgressListener progressListener) {
                cancelled = false;
+               this.progressListener = progressListener;
                fileScanner = new FileScanner(project);
                fileScanner.addFileScannerListener(this);
                new Thread(fileScanner).start();
@@ -262,148 +303,47 @@ public class ProjectInserter implements FileScannerListener, Runnable {
        private InputStream createFileInputStream(String filename, FileOption fileOption, int edition, long[] length) throws IOException {
                File file = new File(project.getLocalPath(), filename);
                length[0] = file.length();
-               if (!fileOption.getReplaceEdition()) {
-                       return new FileInputStream(file);
-               }
-               ByteArrayOutputStream filteredByteOutputStream = new ByteArrayOutputStream(Math.min(Integer.MAX_VALUE, (int) length[0]));
-               ReplacingOutputStream outputStream = new ReplacingOutputStream(filteredByteOutputStream);
-               FileInputStream fileInput = new FileInputStream(file);
-               try {
-                       outputStream.addReplacement("$[EDITION]", String.valueOf(edition));
-                       outputStream.addReplacement("$[URI]", project.getFinalRequestURI(0));
-                       for (int index = 1; index <= fileOption.getEditionRange(); index++) {
-                               outputStream.addReplacement("$[URI+" + index + "]", project.getFinalRequestURI(index));
-                               outputStream.addReplacement("$[EDITION+" + index + "]", String.valueOf(edition + index));
-                       }
-                       StreamCopier.copy(fileInput, outputStream, length[0]);
-               } finally {
-                       Closer.close(fileInput);
-                       Closer.close(outputStream);
-                       Closer.close(filteredByteOutputStream);
-               }
-               byte[] filteredBytes = filteredByteOutputStream.toByteArray();
-               length[0] = filteredBytes.length;
-               return new ByteArrayInputStream(filteredBytes);
-       }
-
-       /**
-        * Creates an input stream for a container.
-        *
-        * @param containerFiles
-        *            All container definitions
-        * @param containerName
-        *            The name of the container to create
-        * @param edition
-        *            The current edition
-        * @param containerLength
-        *            An array containing a single long which is used to
-        *            <em>return</em> the final length of the container stream,
-        *            after all replacements
-        * @return The input stream for the container
-        * @throws IOException
-        *             if an I/O error occurs
-        */
-       private InputStream createContainerInputStream(Map<String, List<String>> containerFiles, String containerName, int edition, long[] containerLength) throws IOException {
-               File tempFile = File.createTempFile("jsite", ".zip", (tempDirectory == null) ? null : new File(tempDirectory));
-               tempFile.deleteOnExit();
-               FileOutputStream fileOutputStream = new FileOutputStream(tempFile);
-               ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream);
-               try {
-                       for (String filename : containerFiles.get(containerName)) {
-                               File dataFile = new File(project.getLocalPath(), filename);
-                               if (dataFile.exists()) {
-                                       ZipEntry zipEntry = new ZipEntry(filename);
-                                       long[] fileLength = new long[1];
-                                       InputStream wrappedInputStream = createFileInputStream(filename, project.getFileOption(filename), edition, fileLength);
-                                       try {
-                                               zipOutputStream.putNextEntry(zipEntry);
-                                               StreamCopier.copy(wrappedInputStream, zipOutputStream, fileLength[0]);
-                                       } finally {
-                                               zipOutputStream.closeEntry();
-                                               wrappedInputStream.close();
-                                       }
-                               }
-                       }
-               } finally {
-                       zipOutputStream.closeEntry();
-                       Closer.close(zipOutputStream);
-                       Closer.close(fileOutputStream);
-               }
-
-               containerLength[0] = tempFile.length();
-               return new FileInputStream(tempFile);
+               return new FileInputStream(file);
        }
 
        /**
         * Creates a file entry suitable for handing in to
         * {@link ClientPutComplexDir#addFileEntry(FileEntry)}.
         *
-        * @param filename
-        *            The name of the file to insert
+        * @param file
+        *            The name and hash of the file to insert
         * @param edition
         *            The current edition
-        * @param containerFiles
-        *            The container definitions
         * @return A file entry for the given file
         */
-       private FileEntry createFileEntry(String filename, int edition, Map<String, List<String>> containerFiles) {
+       private FileEntry createFileEntry(ScannedFile file, int edition) {
                FileEntry fileEntry = null;
+               String filename = file.getFilename();
                FileOption fileOption = project.getFileOption(filename);
-               if (filename.startsWith("/container/:")) {
-                       String containerName = filename.substring("/container/:".length());
+               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[] containerLength = new long[1];
-                               InputStream containerInputStream = createContainerInputStream(containerFiles, containerName, edition, containerLength);
-                               fileEntry = new DirectFileEntry(containerName + ".zip", "application/zip", containerInputStream, containerLength[0]);
+                               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.isInsert()) {
-                               try {
-                                       long[] fileLength = new long[1];
-                                       InputStream fileEntryInputStream = createFileInputStream(filename, fileOption, edition, fileLength);
-                                       fileEntry = new DirectFileEntry(filename, project.getFileOption(filename).getMimeType(), fileEntryInputStream, fileLength[0]);
-                               } catch (IOException ioe1) {
-                                       /* ignore, null is returned. */
-                               }
-                       } else {
-                               if (fileOption.isInsertRedirect()) {
-                                       fileEntry = new RedirectFileEntry(filename, fileOption.getMimeType(), fileOption.getCustomKey());
-                               }
+                       if (fileOption.isInsertRedirect()) {
+                               fileEntry = new RedirectFileEntry(fileOption.hasChangedName() ? fileOption.getChangedName() : filename, fileOption.getMimeType(), fileOption.getCustomKey());
                        }
                }
                return fileEntry;
        }
 
        /**
-        * Creates container definitions.
-        *
-        * @param files
-        *            The list of all files
-        * @param containers
-        *            The list of all containers
-        * @param containerFiles
-        *            Empty map that will be filled with container definitions
-        */
-       private void createContainers(List<String> files, List<String> containers, Map<String, List<String>> containerFiles) {
-               for (String filename : new ArrayList<String>(files)) {
-                       FileOption fileOption = project.getFileOption(filename);
-                       String containerName = fileOption.getContainer();
-                       if (!containerName.equals("")) {
-                               if (!containers.contains(containerName)) {
-                                       containers.add(containerName);
-                                       containerFiles.put(containerName, new ArrayList<String>());
-                                       /* hmm. looks like a hack to me. */
-                                       files.add("/container/:" + containerName);
-                               }
-                               containerFiles.get(containerName).add(filename);
-                               files.remove(filename);
-                       }
-               }
-       }
-
-       /**
         * 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.
@@ -429,17 +369,14 @@ public class ProjectInserter implements FileScannerListener, Runnable {
                        }
                }
                String indexFile = project.getIndexFile();
-               boolean hasIndexFile = (indexFile != null);
-               if (hasIndexFile && !project.getFileOption(indexFile).getContainer().equals("")) {
-                       checkReport.addIssue("warning.container-index", false);
-               }
+               boolean hasIndexFile = (indexFile != null) && (indexFile.length() > 0);
                List<String> allowedIndexContentTypes = Arrays.asList("text/html", "application/xhtml+xml");
                if (hasIndexFile && !allowedIndexContentTypes.contains(project.getFileOption(indexFile).getMimeType())) {
                        checkReport.addIssue("warning.index-not-html", false);
                }
                Map<String, FileOption> fileOptions = project.getFileOptions();
                Set<Entry<String, FileOption>> fileOptionEntries = fileOptions.entrySet();
-               boolean insert = false;
+               boolean insert = fileOptionEntries.isEmpty();
                for (Entry<String, FileOption> fileOptionEntry : fileOptionEntries) {
                        String fileName = fileOptionEntry.getKey();
                        FileOption fileOption = fileOptionEntry.getValue();
@@ -478,7 +415,7 @@ public class ProjectInserter implements FileScannerListener, Runnable {
         */
        public void run() {
                fireProjectInsertStarted();
-               List<String> files = fileScanner.getFiles();
+               List<ScannedFile> files = fileScanner.getFiles();
 
                /* create connection to node */
                synchronized (lockObject) {
@@ -500,11 +437,6 @@ public class ProjectInserter implements FileScannerListener, Runnable {
 
                Client client = new Client(connection);
 
-               /* create containers */
-               final List<String> containers = new ArrayList<String>();
-               final Map<String, List<String>> containerFiles = new HashMap<String, List<String>>();
-               createContainers(files, containers, containerFiles);
-
                /* collect files */
                int edition = project.getEdition();
                String dirURI = "USK@" + project.getInsertURI() + "/" + project.getPath() + "/" + edition + "/";
@@ -514,9 +446,11 @@ public class ProjectInserter implements FileScannerListener, Runnable {
                }
                putDir.setVerbosity(Verbosity.ALL);
                putDir.setMaxRetries(-1);
-               putDir.setEarlyEncode(false);
-               for (String filename : files) {
-                       FileEntry fileEntry = createFileEntry(filename, edition, containerFiles);
+               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);
@@ -529,7 +463,8 @@ public class ProjectInserter implements FileScannerListener, Runnable {
 
                /* start request */
                try {
-                       client.execute(putDir);
+                       client.execute(putDir, progressListener);
+                       fireProjectUploadFinished();
                } catch (IOException ioe1) {
                        fireProjectInsertFinished(false, ioe1);
                        return;
@@ -537,17 +472,12 @@ public class ProjectInserter implements FileScannerListener, Runnable {
 
                /* parse progress and success messages */
                String finalURI = null;
-               boolean firstMessage = true;
                boolean success = false;
                boolean finished = false;
                boolean disconnected = false;
                while (!finished && !cancelled) {
                        Message message = client.readMessage();
                        finished = (message == null) || (disconnected = client.isDisconnected());
-                       if (firstMessage) {
-                               fireProjectUploadFinished();
-                               firstMessage = false;
-                       }
                        logger.log(Level.FINE, "Received message: " + message);
                        if (!finished) {
                                @SuppressWarnings("null")
@@ -564,8 +494,8 @@ public class ProjectInserter implements FileScannerListener, Runnable {
                                        boolean finalized = Boolean.parseBoolean(message.get("FinalizedTotal"));
                                        fireProjectInsertProgress(succeeded, failed, fatal, total, finalized);
                                }
-                               success = "PutSuccessful".equals(messageName);
-                               finished = success || "PutFailed".equals(messageName) || messageName.endsWith("Error");
+                               success |= "PutSuccessful".equals(messageName);
+                               finished = (success && (finalURI != null)) || "PutFailed".equals(messageName) || messageName.endsWith("Error");
                        }
                }
 
@@ -576,6 +506,7 @@ public class ProjectInserter implements FileScannerListener, Runnable {
                        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));
        }
index a80eb4f..c49f166 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * jSite-remote - UpdateChecker.java -
- * Copyright © 2008 David Roden
+ * 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
@@ -52,7 +51,7 @@ public class UpdateChecker implements Runnable {
        private static int counter = 0;
 
        /** The edition for the update check URL. */
-       private static final int UPDATE_EDITION = 11;
+       private static final int UPDATE_EDITION = 17;
 
        /** The URL for update checks. */
        private static final String UPDATE_KEY = "USK@e3myoFyp5avg6WYN16ImHri6J7Nj8980Fm~aQe4EX1U,QvbWT0ImE0TwLODTl7EoJx2NBnwDxTbLTE6zkB-eGPs,AQACAAE";
@@ -216,6 +215,9 @@ public class UpdateChecker implements Runnable {
                                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];
index fbf7b91..a37440c 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * jSite-remote - UpdateListener.java -
- * Copyright © 2008 David Roden
+ * jSite - UpdateListener.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
index 8ef1340..1790fbf 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * jSite - a tool for uploading websites into Freenet
- * Copyright (C) 2006 David Roden
+ * 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
@@ -21,13 +20,23 @@ 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 de.todesbaum.jsite.application.Project;
 import de.todesbaum.jsite.i18n.I18n;
+import de.todesbaum.util.io.Closer;
+import de.todesbaum.util.io.StreamCopier;
 
 /**
  * Scans the local path of a project anychronously and returns the list of found
@@ -39,6 +48,9 @@ import de.todesbaum.jsite.i18n.I18n;
  */
 public class FileScanner implements Runnable {
 
+       /** The logger. */
+       private final static Logger logger = Logger.getLogger(FileScanner.class.getName());
+
        /** The list of listeners. */
        private final List<FileScannerListener> fileScannerListeners = new ArrayList<FileScannerListener>();
 
@@ -46,7 +58,7 @@ public class FileScanner implements Runnable {
        private final Project project;
 
        /** The list of found files. */
-       private List<String> files;
+       private List<ScannedFile> files;
 
        /** Wether there was an error. */
        private boolean error = false;
@@ -99,7 +111,7 @@ public class FileScanner implements Runnable {
         * @see FileScannerListener#fileScannerFinished(FileScanner)
         */
        public void run() {
-               files = new ArrayList<String>();
+               files = new ArrayList<ScannedFile>();
                error = false;
                try {
                        scanFiles(new File(project.getLocalPath()), files);
@@ -125,7 +137,7 @@ public class FileScanner implements Runnable {
         *
         * @return The list of found files
         */
-       public List<String> getFiles() {
+       public List<ScannedFile> getFiles() {
                return files;
        }
 
@@ -139,7 +151,7 @@ public class FileScanner implements Runnable {
         * @throws IOException
         *             if an I/O error occurs
         */
-       private void scanFiles(File rootDir, List<String> fileList) throws IOException {
+       private void scanFiles(File rootDir, List<ScannedFile> fileList) throws IOException {
                File[] files = rootDir.listFiles(new FileFilter() {
 
                        @SuppressWarnings("synthetic-access")
@@ -155,10 +167,178 @@ public class FileScanner implements Runnable {
                                scanFiles(file, fileList);
                                continue;
                        }
-                       String filename = project.shortenFilename(file);
-                       filename = filename.replace('\\', '/');
-                       fileList.add(filename);
+                       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 &lt;bombe@freenetproject.org&gt;
+        */
+       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 &lt;bombe@freenetproject.org&gt;
+        */
+       public static class ScannedFile implements Comparable<ScannedFile> {
+
+               /** 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}
+                */
+               public int compareTo(ScannedFile scannedFile) {
+                       return filename.compareTo(scannedFile.filename);
+               }
+
        }
 
-}
\ No newline at end of file
+}
index 838a2ed..63e2c3d 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * jSite - a tool for uploading websites into Freenet
- * Copyright (C) 2006 David Roden
+ * jSite - FileScannerListener.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
index d6a9404..760c967 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * jSite-0.7 -
- * Copyright (C) 2006 David Roden
+ * jSite - NodeManagerListener.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
@@ -39,4 +38,12 @@ public interface NodeManagerListener extends EventListener {
         */
        public void nodesUpdated(Node[] nodes);
 
+       /**
+        * Notifies a listener that the selected node has changed.
+        *
+        * @param node
+        *            The new selected node
+        */
+       public void nodeSelected(Node node);
+
 }
index 951a912..8e44129 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * jSite-0.7 -
- * Copyright (C) 2006 David Roden
+ * 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
@@ -144,6 +143,18 @@ public class NodeManagerPage extends TWizardPage implements ListSelectionListene
        }
 
        /**
+        * 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() {
@@ -331,6 +342,7 @@ public class NodeManagerPage extends TWizardPage implements ListSelectionListene
        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());
        }
@@ -346,9 +358,12 @@ public class NodeManagerPage extends TWizardPage implements ListSelectionListene
                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);
        }
 
@@ -370,7 +385,7 @@ public class NodeManagerPage extends TWizardPage implements ListSelectionListene
                                nodeNameTextField.setEnabled(enabled);
                                nodeHostnameTextField.setEnabled(enabled);
                                nodePortSpinner.setEnabled(enabled);
-                               deleteNodeAction.setEnabled(enabled);
+                               deleteNodeAction.setEnabled(enabled && (nodeListModel.size() > 1));
                                if (enabled) {
                                        nodeNameTextField.setText(node.getName());
                                        nodeHostnameTextField.setText(node.getHostname());
@@ -426,6 +441,7 @@ public class NodeManagerPage extends TWizardPage implements ListSelectionListene
                        JSpinner sourceSpinner = (JSpinner) source;
                        if ("node-port".equals(sourceSpinner.getName())) {
                                selectedNode.setPort((Integer) sourceSpinner.getValue());
+                               fireNodeSelected(selectedNode);
                                nodeList.repaint();
                        }
                }
index c4b4ce3..5cf258a 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * jSite - PreferencesPage.java -
- * Copyright © 2009 David Roden
+ * 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
@@ -27,8 +26,11 @@ 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;
@@ -37,6 +39,9 @@ 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;
 
@@ -56,18 +61,60 @@ public class PreferencesPage extends TWizardPage {
        /** 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.
         *
@@ -124,6 +171,119 @@ public class PreferencesPage extends TWizardPage {
        }
 
        /**
+        * 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
@@ -179,6 +339,41 @@ public class PreferencesPage extends TWizardPage {
                                chooseTempDirectory();
                        }
                };
+               nextToJarFileAction = new AbstractAction(I18n.getMessage("jsite.preferences.config-directory.jar")) {
+
+                       @SuppressWarnings("synthetic-access")
+                       public void actionPerformed(ActionEvent actionevent) {
+                               configurationLocation = ConfigurationLocation.NEXT_TO_JAR_FILE;
+                       }
+               };
+               homeDirectoryAction = new AbstractAction(I18n.getMessage("jsite.preferences.config-directory.home")) {
+
+                       @SuppressWarnings("synthetic-access")
+                       public void actionPerformed(ActionEvent actionevent) {
+                               configurationLocation = ConfigurationLocation.HOME_DIRECTORY;
+                       }
+               };
+               customDirectoryAction = new AbstractAction(I18n.getMessage("jsite.preferences.config-directory.custom")) {
+
+                       @SuppressWarnings("synthetic-access")
+                       public void actionPerformed(ActionEvent actionEvent) {
+                               configurationLocation = ConfigurationLocation.CUSTOM;
+                       }
+               };
+               useEarlyEncodeAction = new AbstractAction(I18n.getMessage("jsite.preferences.insert-options.use-early-encode")) {
+
+                       @SuppressWarnings("synthetic-access")
+                       public void actionPerformed(ActionEvent actionEvent) {
+                               useEarlyEncode = useEarlyEncodeCheckBox.isSelected();
+                       }
+               };
+               priorityAction = new AbstractAction(I18n.getMessage("jsite.preferences.insert-options.priority")) {
+
+                       @SuppressWarnings("synthetic-access")
+                       public void actionPerformed(ActionEvent actionEvent) {
+                               priority = (PriorityClass) insertPriorityComboBox.getSelectedItem();
+                       }
+               };
 
                I18nContainer.getInstance().registerRunnable(new Runnable() {
 
@@ -187,6 +382,10 @@ public class PreferencesPage extends TWizardPage {
                                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"));
                        }
                });
        }
@@ -197,19 +396,17 @@ public class PreferencesPage extends TWizardPage {
         * @return The preferences panel
         */
        private JPanel createPreferencesPanel() {
-               JPanel preferencesPanel = new JPanel(new BorderLayout(12, 12));
-
-               JPanel tempDirectoryPanel = new JPanel(new GridBagLayout());
-               preferencesPanel.add(tempDirectoryPanel, BorderLayout.CENTER);
+               JPanel preferencesPanel = new JPanel(new GridBagLayout());
+               preferencesPanel.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
 
                final JLabel tempDirectoryLabel = new JLabel("<html><b>" + I18n.getMessage("jsite.preferences.temp-directory") + "</b></html>");
-               tempDirectoryPanel.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));
+               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);
-               tempDirectoryPanel.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));
+               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);
-               tempDirectoryPanel.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));
+               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);
@@ -224,10 +421,46 @@ public class PreferencesPage extends TWizardPage {
                        defaultTempDirectory.setSelected(true);
                }
                chooseTempDirectoryAction.setEnabled(tempDirectory != null);
-               tempDirectoryPanel.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));
+               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);
-               tempDirectoryPanel.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));
+               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("<html><b>" + I18n.getMessage("jsite.preferences.config-directory") + "</b></html>");
+               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("<html><b>" + I18n.getMessage("jsite.preferences.insert-options") + "</b></html>");
+               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() {
 
@@ -236,6 +469,10 @@ public class PreferencesPage extends TWizardPage {
                         */
                        public void run() {
                                tempDirectoryLabel.setText("<html><b>" + I18n.getMessage("jsite.preferences.temp-directory") + "</b></html>");
+                               configurationDirectoryLabel.setText("<html><b>" + I18n.getMessage("jsite.preferences.config-directory") + "</b></html>");
+                               insertOptionsLabel.setText("<html><b>" + I18n.getMessage("jsite.preferences.insert-options") + "</b></html>");
+                               insertPriorityLabel.setText(I18n.getMessage("jsite.preferences.insert-options.priority"));
+                               manifestPutterLabel.setText(I18n.getMessage("jsite.preferences.insert-options.manifest-putter"));
                        }
                });
 
index fd9ae2f..80bb52c 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * jSite - a tool for uploading websites into Freenet
- * Copyright (C) 2006 David Roden
+ * 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
@@ -21,7 +20,6 @@ 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;
@@ -29,8 +27,6 @@ import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.KeyEvent;
 import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -38,7 +34,6 @@ import java.util.Set;
 
 import javax.swing.AbstractAction;
 import javax.swing.Action;
-import javax.swing.DefaultComboBoxModel;
 import javax.swing.JButton;
 import javax.swing.JCheckBox;
 import javax.swing.JComboBox;
@@ -48,14 +43,9 @@ 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.SwingUtilities;
-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;
@@ -65,6 +55,7 @@ import javax.swing.text.Document;
 
 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.mime.DefaultMIMETypes;
@@ -77,7 +68,7 @@ import de.todesbaum.util.swing.TWizardPage;
  *
  * @author David ‘Bombe’ Roden &lt;bombe@freenetproject.org&gt;
  */
-public class ProjectFilesPage extends TWizardPage implements ActionListener, ListSelectionListener, DocumentListener, FileScannerListener, ChangeListener {
+public class ProjectFilesPage extends TWizardPage implements ActionListener, ListSelectionListener, DocumentListener, FileScannerListener {
 
        /** The project. */
        private Project project;
@@ -85,15 +76,6 @@ public class ProjectFilesPage extends TWizardPage implements ActionListener, Lis
        /** The “scan files” action. */
        private Action scanAction;
 
-       /** The “edit container” action. */
-       private Action editContainerAction;
-
-       /** The “add container” action. */
-       private Action addContainerAction;
-
-       /** The “delete container” action. */
-       protected Action deleteContainerAction;
-
        /** The “ignore hidden files” checkbox. */
        private JCheckBox ignoreHiddenFilesCheckBox;
 
@@ -106,6 +88,9 @@ public class ProjectFilesPage extends TWizardPage implements ActionListener, Lis
        /** The “insert” checkbox. */
        private JCheckBox fileOptionsInsertCheckBox;
 
+       /** The “force insert” checkbox. */
+       private JCheckBox fileOptionsForceInsertCheckBox;
+
        /** The “insert redirect” checkbox. */
        private JCheckBox fileOptionsInsertRedirectCheckBox;
 
@@ -121,18 +106,6 @@ public class ProjectFilesPage extends TWizardPage implements ActionListener, Lis
        /** The “mime type” combo box. */
        private JComboBox fileOptionsMIMETypeComboBox;
 
-       /** The “mime type” combo box model. */
-       private DefaultComboBoxModel containerComboBoxModel;
-
-       /** The “container” combo box. */
-       private JComboBox fileOptionsContainerComboBox;
-
-       /** The “edition replacement range” spinner. */
-       private JSpinner replaceEditionRangeSpinner;
-
-       /** The “replacement” check box. */
-       private JCheckBox replacementCheckBox;
-
        /**
         * Creates a new project file page.
         *
@@ -167,48 +140,12 @@ public class ProjectFilesPage extends TWizardPage implements ActionListener, Lis
                scanAction.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_S);
                scanAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project-files.action.rescan.tooltip"));
 
-               addContainerAction = new AbstractAction(I18n.getMessage("jsite.project-files.action.add-container")) {
-
-                       @SuppressWarnings("synthetic-access")
-                       public void actionPerformed(ActionEvent actionEvent) {
-                               actionAddContainer();
-                       }
-               };
-               addContainerAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project-files.action.add-container.tooltip"));
-               addContainerAction.setEnabled(false);
-
-               editContainerAction = new AbstractAction(I18n.getMessage("jsite.project-files.action.edit-container")) {
-
-                       @SuppressWarnings("synthetic-access")
-                       public void actionPerformed(ActionEvent actionEvent) {
-                               actionEditContainer();
-                       }
-               };
-               editContainerAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project-files.action.edit-container.tooltip"));
-               editContainerAction.setEnabled(false);
-
-               deleteContainerAction = new AbstractAction(I18n.getMessage("jsite.project-files.action.delete-container")) {
-
-                       @SuppressWarnings("synthetic-access")
-                       public void actionPerformed(ActionEvent actionEvent) {
-                               actionDeleteContainer();
-                       }
-               };
-               deleteContainerAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project-files.action.delete-container.tooltip"));
-               deleteContainerAction.setEnabled(false);
-
                I18nContainer.getInstance().registerRunnable(new Runnable() {
 
                        @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"));
-                               addContainerAction.putValue(Action.NAME, I18n.getMessage("jsite.project-files.action.add-container"));
-                               addContainerAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project-files.action.add-container.tooltip"));
-                               editContainerAction.putValue(Action.NAME, I18n.getMessage("jsite.project-files.action.edit-container"));
-                               editContainerAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project-files.action.edit-container.tooltip"));
-                               deleteContainerAction.putValue(Action.NAME, I18n.getMessage("jsite.project-files.action.delete-container"));
-                               deleteContainerAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project-files.action.delete-container.tooltip"));
                        }
                });
        }
@@ -272,6 +209,15 @@ public class ProjectFilesPage extends TWizardPage implements ActionListener, Lis
 
                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);
@@ -285,9 +231,9 @@ public class ProjectFilesPage extends TWizardPage implements ActionListener, Lis
                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, 5, 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, 5, 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, 5, 3, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0));
+               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"));
@@ -303,6 +249,10 @@ public class ProjectFilesPage extends TWizardPage implements ActionListener, Lis
                        @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 {
@@ -326,8 +276,8 @@ public class ProjectFilesPage extends TWizardPage implements ActionListener, Lis
 
                });
 
-               fileOptionsPanel.add(fileOptionsRenameCheckBox, new GridBagConstraints(0, 6, 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, 6, 3, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0));
+               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(DefaultMIMETypes.getAllMIMETypes());
                fileOptionsMIMETypeComboBox.setToolTipText(I18n.getMessage("jsite.project-files.mime-type.tooltip"));
@@ -337,54 +287,8 @@ public class ProjectFilesPage extends TWizardPage implements ActionListener, Lis
                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, 7, 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, 7, 4, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0));
-
-               containerComboBoxModel = new DefaultComboBoxModel();
-               fileOptionsContainerComboBox = new JComboBox(containerComboBoxModel);
-               fileOptionsContainerComboBox.setToolTipText(I18n.getMessage("jsite.project-files.container.tooltip"));
-               fileOptionsContainerComboBox.setName("project-files.container");
-               fileOptionsContainerComboBox.addActionListener(this);
-               fileOptionsContainerComboBox.setEnabled(false);
-               fileOptionsContainerComboBox.setVisible(false);
-
-               final TLabel containerLabel = new TLabel(I18n.getMessage("jsite.project-files.container") + ":", KeyEvent.VK_C, fileOptionsContainerComboBox);
-               containerLabel.setVisible(false);
-               JButton addContainerButton = new JButton(addContainerAction);
-               addContainerButton.setVisible(false);
-               JButton editContainerButton = new JButton(editContainerAction);
-               editContainerButton.setVisible(false);
-               JButton deleteContainerButton = new JButton(deleteContainerAction);
-               deleteContainerButton.setVisible(false);
-               fileOptionsPanel.add(containerLabel, 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(fileOptionsContainerComboBox, new GridBagConstraints(1, 8, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0));
-               fileOptionsPanel.add(addContainerButton, new GridBagConstraints(2, 8, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0));
-               fileOptionsPanel.add(editContainerButton, new GridBagConstraints(3, 8, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0));
-               fileOptionsPanel.add(deleteContainerButton, new GridBagConstraints(4, 8, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0));
-
-               JPanel fileOptionsReplacementPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 6, 6));
-               fileOptionsReplacementPanel.setBorder(new EmptyBorder(-6, -6, -6, -6));
-
-               replacementCheckBox = new JCheckBox(I18n.getMessage("jsite.project-files.replacement"));
-               replacementCheckBox.setName("project-files.replace-edition");
-               replacementCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.replacement.tooltip"));
-               replacementCheckBox.addActionListener(this);
-               replacementCheckBox.setEnabled(false);
-               replacementCheckBox.setVisible(false);
-               fileOptionsReplacementPanel.add(replacementCheckBox);
-
-               replaceEditionRangeSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 99, 1));
-               replaceEditionRangeSpinner.setName("project-files.replace-edition-range");
-               replaceEditionRangeSpinner.setToolTipText(I18n.getMessage("jsite.project-files.replacement.edition-range.tooltip"));
-               replaceEditionRangeSpinner.addChangeListener(this);
-               replaceEditionRangeSpinner.setEnabled(false);
-               replaceEditionRangeSpinner.setVisible(false);
-               final JLabel editionRangeLabel = new JLabel(I18n.getMessage("jsite.project-files.replacement.edition-range"));
-               editionRangeLabel.setVisible(false);
-               fileOptionsReplacementPanel.add(editionRangeLabel);
-               fileOptionsReplacementPanel.add(replaceEditionRangeSpinner);
-
-               fileOptionsPanel.add(fileOptionsReplacementPanel, new GridBagConstraints(0, 9, 5, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 18, 0, 0), 0, 0));
+               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() {
 
@@ -397,6 +301,8 @@ public class ProjectFilesPage extends TWizardPage implements ActionListener, Lis
                                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"));
@@ -405,12 +311,6 @@ public class ProjectFilesPage extends TWizardPage implements ActionListener, Lis
                                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") + ":");
-                               fileOptionsContainerComboBox.setToolTipText(I18n.getMessage("jsite.project-files.container.tooltip"));
-                               containerLabel.setText(I18n.getMessage("jsite.project-files.container") + ":");
-                               replacementCheckBox.setText(I18n.getMessage("jsite.project-files.replacement"));
-                               replacementCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.replacement.tooltip"));
-                               replaceEditionRangeSpinner.setToolTipText(I18n.getMessage("jsite.project-files.replacement.edition-range.tooltip"));
-                               editionRangeLabel.setText(I18n.getMessage("jsite.project-files.replacement.edition-range"));
                        }
                });
 
@@ -437,42 +337,6 @@ public class ProjectFilesPage extends TWizardPage implements ActionListener, Lis
                });
        }
 
-       /**
-        * Returns a list of all project files.
-        *
-        * @return All project files
-        */
-       private List<String> getProjectFiles() {
-               List<String> files = new ArrayList<String>();
-               for (int index = 0, size = projectFileList.getModel().getSize(); index < size; index++) {
-                       files.add((String) projectFileList.getModel().getElementAt(index));
-               }
-               return files;
-       }
-
-       /**
-        * Updates the container combo box model.
-        */
-       private void rebuildContainerComboBox() {
-               /* scan files for containers */
-               List<String> files = getProjectFiles();
-               List<String> containers = new ArrayList<String>(); // ComboBoxModel
-               // sucks. No
-               // contains()!
-               containers.add("");
-               for (String filename : files) {
-                       String container = project.getFileOption(filename).getContainer();
-                       if (!containers.contains(container)) {
-                               containers.add(container);
-                       }
-               }
-               Collections.sort(containers);
-               containerComboBoxModel.removeAllElements();
-               for (String container : containers) {
-                       containerComboBoxModel.addElement(container);
-               }
-       }
-
        //
        // ACTIONS
        //
@@ -494,66 +358,6 @@ public class ProjectFilesPage extends TWizardPage implements ActionListener, Lis
        }
 
        /**
-        * Adds a container.
-        */
-       private void actionAddContainer() {
-               String containerName = JOptionPane.showInputDialog(wizard, I18n.getMessage("jsite.project-files.action.add-container.message") + ":", null, JOptionPane.INFORMATION_MESSAGE);
-               if (containerName == null) {
-                       return;
-               }
-               containerName = containerName.trim();
-               String filename = (String) projectFileList.getSelectedValue();
-               FileOption fileOption = project.getFileOption(filename);
-               fileOption.setContainer(containerName);
-               rebuildContainerComboBox();
-               fileOptionsContainerComboBox.setSelectedItem(containerName);
-       }
-
-       /**
-        * Edits the container.
-        */
-       private void actionEditContainer() {
-               String selectedFilename = (String) projectFileList.getSelectedValue();
-               FileOption fileOption = project.getFileOption(selectedFilename);
-               String oldContainerName = fileOption.getContainer();
-               String containerName = JOptionPane.showInputDialog(wizard, I18n.getMessage("jsite.project-files.action.edit-container.message") + ":", oldContainerName);
-               if (containerName == null) {
-                       return;
-               }
-               if (containerName.equals("")) {
-                       fileOption.setContainer("");
-                       fileOptionsContainerComboBox.setSelectedItem("");
-                       return;
-               }
-               List<String> files = getProjectFiles();
-               for (String filename : files) {
-                       fileOption = project.getFileOption(filename);
-                       if (fileOption.getContainer().equals(oldContainerName)) {
-                               fileOption.setContainer(containerName);
-                       }
-               }
-               rebuildContainerComboBox();
-               fileOptionsContainerComboBox.setSelectedItem(containerName);
-       }
-
-       /**
-        * Deletes the container.
-        */
-       private void actionDeleteContainer() {
-               if (JOptionPane.showConfirmDialog(wizard, I18n.getMessage("jsite.project-files.action.delete-container.message"), null, JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE) == JOptionPane.OK_OPTION) {
-                       String containerName = (String) fileOptionsContainerComboBox.getSelectedItem();
-                       List<String> files = getProjectFiles();
-                       for (String filename : files) {
-                               FileOption fileOption = project.getFileOption(filename);
-                               if (fileOption.getContainer().equals(containerName)) {
-                                       fileOption.setContainer("");
-                               }
-                       }
-                       fileOptionsContainerComboBox.setSelectedItem("");
-               }
-       }
-
-       /**
         * {@inheritDoc}
         * <p>
         * Updates the file list.
@@ -561,21 +365,27 @@ public class ProjectFilesPage extends TWizardPage implements ActionListener, Lis
        public void fileScannerFinished(FileScanner fileScanner) {
                final boolean error = fileScanner.isError();
                if (!error) {
-                       final List<String> files = fileScanner.getFiles();
+                       final List<ScannedFile> files = fileScanner.getFiles();
                        SwingUtilities.invokeLater(new Runnable() {
 
                                @SuppressWarnings("synthetic-access")
                                public void run() {
-                                       projectFileList.setListData(files.toArray(new String[files.size()]));
+                                       projectFileList.setListData(files.toArray());
                                        projectFileList.clearSelection();
-                                       rebuildContainerComboBox();
                                }
                        });
                        Set<String> entriesToRemove = new HashSet<String>();
                        Iterator<String> filenames = new HashSet<String>(project.getFileOptions().keySet()).iterator();
                        while (filenames.hasNext()) {
                                String filename = filenames.next();
-                               if (!files.contains(filename)) {
+                               boolean found = false;
+                               for (ScannedFile scannedFile : files) {
+                                       if (scannedFile.getFilename().equals(filename)) {
+                                               found = true;
+                                               break;
+                                       }
+                               }
+                               if (!found) {
                                        entriesToRemove.add(filename);
                                }
                        }
@@ -604,11 +414,11 @@ public class ProjectFilesPage extends TWizardPage implements ActionListener, Lis
         *         no file is selected
         */
        private FileOption getSelectedFile() {
-               String filename = (String) projectFileList.getSelectedValue();
-               if (filename == null) {
+               ScannedFile scannedFile = (ScannedFile) projectFileList.getSelectedValue();
+               if (scannedFile == null) {
                        return null;
                }
-               return project.getFileOption(filename);
+               return project.getFileOption(scannedFile.getFilename());
        }
 
        //
@@ -625,26 +435,34 @@ public class ProjectFilesPage extends TWizardPage implements ActionListener, Lis
                        actionScan();
                        return;
                }
-               String filename = (String) projectFileList.getSelectedValue();
-               if (filename == null) {
+               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()) {
-                                       project.setIndexFile(filename);
+                                       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 {
-                                       project.setIndexFile(null);
+                                       if (filename.equals(project.getIndexFile())) {
+                                               project.setIndexFile(null);
+                                       }
                                }
                        } else if ("insert".equals(checkBox.getName())) {
                                boolean isInsert = checkBox.isSelected();
                                fileOption.setInsert(isInsert);
-                               if (!isInsert) {
-                                       fileOptionsContainerComboBox.setSelectedItem("");
-                               }
                                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);
@@ -653,24 +471,11 @@ public class ProjectFilesPage extends TWizardPage implements ActionListener, Lis
                                boolean isRenamed = checkBox.isSelected();
                                fileOptionsRenameTextField.setEnabled(isRenamed);
                                fileOption.setChangedName(isRenamed ? fileOptionsRenameTextField.getText() : "");
-                       } else if ("project-files.replace-edition".equals(checkBox.getName())) {
-                               boolean replaceEdition = checkBox.isSelected();
-                               fileOption.setReplaceEdition(replaceEdition);
-                               replaceEditionRangeSpinner.setEnabled(replaceEdition);
                        }
                } else if (source instanceof JComboBox) {
                        JComboBox comboBox = (JComboBox) source;
                        if ("project-files.mime-type".equals(comboBox.getName())) {
                                fileOption.setMimeType((String) comboBox.getSelectedItem());
-                       } else if ("project-files.container".equals(comboBox.getName())) {
-                               String containerName = (String) comboBox.getSelectedItem();
-                               fileOption.setContainer(containerName);
-                               boolean enabled = !"".equals(containerName);
-                               editContainerAction.setEnabled(enabled);
-                               deleteContainerAction.setEnabled(enabled);
-                               if (enabled) {
-                                       fileOptionsInsertCheckBox.setSelected(true);
-                               }
                        }
                }
        }
@@ -682,23 +487,21 @@ public class ProjectFilesPage extends TWizardPage implements ActionListener, Lis
        /**
         * {@inheritDoc}
         */
+       @SuppressWarnings("null")
        public void valueChanged(ListSelectionEvent e) {
-               String filename = (String) projectFileList.getSelectedValue();
-               boolean enabled = filename != null;
-               boolean insert = fileOptionsInsertCheckBox.isSelected();
+               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);
-               fileOptionsContainerComboBox.setEnabled(enabled);
-               addContainerAction.setEnabled(enabled);
-               editContainerAction.setEnabled(enabled);
-               deleteContainerAction.setEnabled(enabled);
-               replacementCheckBox.setEnabled(enabled && insert);
                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());
@@ -707,13 +510,11 @@ public class ProjectFilesPage extends TWizardPage implements ActionListener, Lis
                        fileOptionsRenameTextField.setEnabled(fileOption.hasChangedName());
                        fileOptionsRenameTextField.setText(fileOption.getChangedName());
                        fileOptionsMIMETypeComboBox.getModel().setSelectedItem(fileOption.getMimeType());
-                       fileOptionsContainerComboBox.setSelectedItem(fileOption.getContainer());
-                       replacementCheckBox.setSelected(fileOption.getReplaceEdition());
-                       replaceEditionRangeSpinner.setValue(fileOption.getEditionRange());
-                       replaceEditionRangeSpinner.setEnabled(fileOption.getReplaceEdition());
                } else {
                        defaultFileCheckBox.setSelected(false);
                        fileOptionsInsertCheckBox.setSelected(true);
+                       fileOptionsForceInsertCheckBox.setEnabled(false);
+                       fileOptionsForceInsertCheckBox.setSelected(false);
                        fileOptionsInsertRedirectCheckBox.setEnabled(false);
                        fileOptionsInsertRedirectCheckBox.setSelected(false);
                        fileOptionsCustomKeyTextField.setEnabled(false);
@@ -723,9 +524,6 @@ public class ProjectFilesPage extends TWizardPage implements ActionListener, Lis
                        fileOptionsRenameTextField.setEnabled(false);
                        fileOptionsRenameTextField.setText("");
                        fileOptionsMIMETypeComboBox.getModel().setSelectedItem(DefaultMIMETypes.DEFAULT_MIME_TYPE);
-                       fileOptionsContainerComboBox.setSelectedItem("");
-                       replacementCheckBox.setSelected(false);
-                       replaceEditionRangeSpinner.setValue(0);
                }
        }
 
@@ -741,11 +539,11 @@ public class ProjectFilesPage extends TWizardPage implements ActionListener, Lis
         *            The document event to process
         */
        private void processDocumentUpdate(DocumentEvent documentEvent) {
-               String filename = (String) projectFileList.getSelectedValue();
-               if (filename == null) {
+               ScannedFile scannedFile = (ScannedFile) projectFileList.getSelectedValue();
+               if (scannedFile == null) {
                        return;
                }
-               FileOption fileOption = project.getFileOption(filename);
+               FileOption fileOption = project.getFileOption(scannedFile.getFilename());
                Document document = documentEvent.getDocument();
                try {
                        String text = document.getText(0, document.getLength());
@@ -776,24 +574,4 @@ public class ProjectFilesPage extends TWizardPage implements ActionListener, Lis
                processDocumentUpdate(documentEvent);
        }
 
-       //
-       // INTERFACE ChangeListener
-       //
-
-       /**
-        * {@inheritDoc}
-        */
-       public void stateChanged(ChangeEvent changeEvent) {
-               String filename = (String) projectFileList.getSelectedValue();
-               if (filename == null) {
-                       return;
-               }
-               FileOption fileOption = project.getFileOption(filename);
-               Object source = changeEvent.getSource();
-               if (source instanceof JSpinner) {
-                       JSpinner spinner = (JSpinner) source;
-                       fileOption.setEditionRange((Integer) spinner.getValue());
-               }
-       }
-
 }
index 2e9286e..bfd8b89 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * jSite - a tool for uploading websites into Freenet
- * Copyright (C) 2006 David Roden
+ * 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
@@ -55,6 +54,9 @@ 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.io.StreamCopier.ProgressListener;
 import de.todesbaum.util.swing.TWizard;
 import de.todesbaum.util.swing.TWizardPage;
 
@@ -220,7 +222,27 @@ public class ProjectInsertPage extends TWizardPage implements InsertListener, Cl
                progressBar.setValue(0);
                progressBar.setString(I18n.getMessage("jsite.insert.starting"));
                progressBar.setFont(progressBar.getFont().deriveFont(Font.PLAIN));
-               projectInserter.start();
+               projectInserter.start(new ProgressListener() {
+
+                       public void onProgress(final long copied, final long length) {
+                               SwingUtilities.invokeLater(new Runnable() {
+
+                                       /**
+                                        * {@inheritDoc}
+                                        */
+                                       @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);
+                                       }
+                               });
+                       }
+               });
        }
 
        /**
@@ -292,6 +314,38 @@ public class ProjectInsertPage extends TWizardPage implements InsertListener, Cl
                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
        //
@@ -315,6 +369,14 @@ public class ProjectInsertPage extends TWizardPage implements InsertListener, Cl
         */
        public void projectUploadFinished(Project project) {
                startTime = System.currentTimeMillis();
+               SwingUtilities.invokeLater(new Runnable() {
+
+                       @SuppressWarnings("synthetic-access")
+                       public void run() {
+                               progressBar.setString(I18n.getMessage("jsite.insert.starting"));
+                               progressBar.setValue(0);
+                       }
+               });
        }
 
        /**
@@ -359,6 +421,9 @@ public class ProjectInsertPage extends TWizardPage implements InsertListener, Cl
 
                        @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;
index 30d0511..98ad847 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * jSite - a tool for uploading websites into Freenet
- * Copyright (C) 2006 David Roden
+ * 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
@@ -609,8 +608,13 @@ public class ProjectPage extends TWizardPage implements ListSelectionListener, D
                        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();
                        }
                }
index 5b16aff..4c1cceb 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * jSite - a tool for uploading websites into Freenet
- * Copyright (C) 2006 David Roden
+ * jSite - I18n.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
index 3676c41..da2e0c3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * jSite-remote - I18nContainer.java Copyright © 2007 David Roden
+ * 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
index 5b1dc7f..9e93e5d 100644 (file)
@@ -1,6 +1,5 @@
 #
-# jSite - a tool for uploading websites into Freenet
-# Copyright (C) 2006 David Roden
+# jSite - jSite.properties - 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
@@ -27,9 +26,6 @@
 # lines single quotes (ASCII 39) needs to be escaped by entering them twice,
 # otherwise the placeholder will not be replaced!
 
-jsite.main.already-running=<html><b>jSite is already running</b><br><br>A lock file has been found that suggests that another<br>instance of jSite is already running. Running multiple instances<br>of jSite is guaranteed to break your configuration.</html>
-jsite.main.already-running.override=Start anyway
-
 jsite.general.ok=OK
 jsite.general.cancel=Cancel
 
@@ -38,6 +34,9 @@ jsite.wizard.next=Next
 jsite.wizard.quit=Quit
 
 jsite.quit.question=Do you really want to quit?
+jsite.quit.question.title=Really quit?
+jsite.quit.overwrite-configuration=<html><b>Overwrite configuration?</b><br><br>A configuration file already exists:<br><code>{0}</code><br><br>Should it be overwritten?</html>
+jsite.quit.overwrite-configuration.title=Overwrite configuration?
 jsite.quit.config-not-saved=<html><b>Configuration not saved</b><br><br>The configuration could not be saved.<br>Do you want to quit anyway?</html>
 
 jsite.menu.languages=Languages
@@ -54,7 +53,7 @@ jsite.menu.help=Help
 jsite.menu.help.check-for-updates=Check for Updates
 jsite.menu.help.about=About
 
-jsite.about.message=<html><big><b>jSite {0}</b></big><br><br>Copyright \u00a9 2006-2010 David Roden<br>Released under the GNU General Public License</html>
+jsite.about.message=<html><big><b>jSite {0}</b></big><br><br>Copyright \u00a9 2006\u20132012 David Roden<br>Released under the GNU General Public License</html>
 
 jsite.node-manager.heading=Node Manager
 jsite.node-manager.description=Manage your nodes here.
@@ -74,6 +73,14 @@ jsite.preferences.temp-directory.default=Default (chosen by system)
 jsite.preferences.temp-directory.custom=Custom
 jsite.preferences.temp-directory.choose=Choose
 jsite.preferences.temp-directory.choose.approve=Choose
+jsite.preferences.config-directory=Location of configuration file
+jsite.preferences.config-directory.jar=Next to the JAR file
+jsite.preferences.config-directory.home=Home directory
+jsite.preferences.config-directory.custom=Custom directory
+jsite.preferences.insert-options=Insert options
+jsite.preferences.insert-options.use-early-encode=Generate final URI early
+jsite.preferences.insert-options.priority=Priority
+jsite.preferences.insert-options.manifest-putter=Manifest Putter
 
 jsite.insert.heading=Project insert
 jsite.insert.description=Please wait while the project is being inserted.
@@ -126,7 +133,7 @@ jsite.project.project.path=Freesite Path
 jsite.project.project.edition=Edition
 jsite.project.project.uri=URI
 jsite.project.keygen.io-error=<html><b>Node communication failure</b><br><br>Communication with the node failed<br>with the following error message:<br><br><code>{0}</code><br><br>Please make sure that you have entered<br>the correct host name and port number<br>on the "Node Settings" page.</html>
-jsite.project.warning.generate-new-key=<html><b>Generate new key?</b><br><br>If you generate a new key, your site will be published<br>under that new key. Any trust that other users put<br>in the old key of your site will be gone!</html>
+jsite.project.warning.generate-new-key=<html><b>Generate new key?</b><br><br>If you generate a new key, your site will be published<br>under that new key. Any trust that other users put<br>in the old key of your site will be gone!<br>Also, the edition will be reset.</html>
 jsite.project.warning.reset-edition=<html><b>Reset edition?</b><br><br>Resetting the edition can lead to insert failures<br>and lots of confusion if you have not changed<br>the path or the keys of the project!</html>
 jsite.project.warning.use-clipboard-now=<html><b>URI copied</b><br><br>Please note that it is possible that quitting jSite<br>now will empty the clipboard. Please use the<br>copied URI immediately in another window!</html>
 
@@ -134,15 +141,6 @@ jsite.project-files.heading=Project Files
 jsite.project-files.description=<html>On this page you can specify parameters for the files within the project, such as<br>externally generated keys or MIME types, if the automatic detection failed.</html>
 jsite.project-files.action.rescan=Re-scan
 jsite.project-files.action.rescan.tooltip=Re-scan the project directory for new files
-jsite.project-files.action.add-container=Add
-jsite.project-files.action.add-container.tooltip=Adds a new container to the project and this file to it
-jsite.project-files.action.add-container.message=Enter the name of the new container
-jsite.project-files.action.edit-container=Edit
-jsite.project-files.action.edit-container.tooltip=Changes the name of the container
-jsite.project-files.action.edit-container.message=Enter the new name of the container
-jsite.project-files.action.delete-container=Delete
-jsite.project-files.action.delete-container.tooltip=Deletes this container
-jsite.project-files.action.delete-container.message=Do you really want to delete this container?
 jsite.project-files.ignore-hidden-files=Ignore hidden files
 jsite.project-files.ignore-hidden-files.tooltip=When selected, hidden files are not inserted
 jsite.project-files.file-options=File Options
@@ -150,6 +148,8 @@ jsite.project-files.default=Default file
 jsite.project-files.default.tooltip=Specify that this file is the project\u2019s index file
 jsite.project-files.insert=Insert
 jsite.project-files.insert.tooltip=Uncheck if you do not want to insert this file
+jsite.project-files.force-insert=Force insert
+jsite.project-files.force-insert.tooltip=Forces the insert of this file even it is not modified
 jsite.project-files.insert-redirect=Redirect
 jsite.project-files.insert-redirect.tooltip=Check if you want to insert a redirect for this file
 jsite.project-files.custom-key=Custom key
@@ -160,12 +160,9 @@ jsite.project-files.mime-type=MIME type
 jsite.project-files.mime-type.tooltip=Select the correct MIME type here if the detection failed
 jsite.project-files.container=Container
 jsite.project-files.container.tooltip=Selects a container for the current file
-jsite.project-files.replacement=Replacements
-jsite.project-files.replacement.tooltip=Activates replacements in file
-jsite.project-files.replacement.edition-range=Range
-jsite.project-files.replacement.edition-range.tooltip=Also replace $[EDITION+1], $[EDITION+2]\u2026
 jsite.project-files.scan-error=<html><b>Error scanning files</b><br><br>Either the directory of the project does not exist<br>or some files/directories in it are not accessible.<br>Please go back and select the correct directory.</html>
 jsite.project-files.insert-now=Insert now
+jsite.project-files.invalid-default-file=Only files in the root directory may be selected as default files.
 
 jsite.update-checker.found-version.title=Found New Version
 jsite.update-checker.found-version.message=<html>A new version was found.<br><br>Version {0} (released {1,date})</html>
@@ -185,7 +182,6 @@ jsite.key-dialog.label.public-key=Public Key
 jsite.key-dialog.label.actions=<html><b>Actions</b></html>
 
 jsite.warning.empty-index=<html><b>No default file</b><br><br>You did not specify a default file for this project.<br>While it is possible to insert a project without a default<br>file you should specify one to ease browsing.</html>
-jsite.warning.container-index=<html><b>Default file in container</b><br><br>Your default file was placed in a container!<br>This might make other people shun your page.</html>
 jsite.warning.index-not-html=<html><b>Default file is not HTML</b><br><br>Your default file does not have the MIME type "text/html"!<br>Loading your Freesite in a browser may give unexpected results.</html>
 
 jsite.error.no-node-selected=<html><b>No node selected</b><br><br>Please select a node from the menu!</html>
index d499982..d6441ca 100644 (file)
@@ -1,6 +1,5 @@
 #
-# jSite - a tool for uploading websites into Freenet
-# Copyright (C) 2006 David Roden
+# jSite - jSite_de.properties - 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
@@ -27,9 +26,6 @@
 # lines single quotes (ASCII 39) needs to be escaped by entering them twice,
 # otherwise the placeholder will not be replaced!
 
-jsite.main.already-running=<html><b>jSite l\u00e4uft bereits!</b><br><br>Es wurde festgestellt, dass jSite bereits l\u00e4uft. Das kann<br>zu Besch\u00e4digungen an der Konfiguration f\u00fchren.</html>
-jsite.main.already-running.override=Trotzdem starten
-
 jsite.general.ok=OK
 jsite.general.cancel=Abbrechen
 
@@ -38,6 +34,9 @@ jsite.wizard.next=Vorw\u00e4rts
 jsite.wizard.quit=Beenden
 
 jsite.quit.question=M\u00f6chten Sie jSite wirklich beenden?
+jsite.quit.question.title=Wirklich beenden?
+jsite.quit.overwrite-configuration=<html><b>Konfiguration \u00fcberschreiben?</b><br><br>Es existiert bereits eine Konfigurationsdatei unter:<br><code>{0}</code><br><br>Soll sie \u00fcberschrieben werden?</html>
+jsite.quit.overwrite-configuration.title=Konfiguration \u00fcberschreiben?
 jsite.quit.config-not-saved=<html><b>Konfiguration nicht gespeichert</b><br><br>Die Konfiguration konnte nicht gespeichert werden.<br>Soll jSite trotzdem beendet werden?</html>
 
 jsite.menu.languages=Sprachen
@@ -54,7 +53,7 @@ jsite.menu.help=Hilfe
 jsite.menu.help.check-for-updates=Auf Updates pr\u00fcfen
 jsite.menu.help.about=\u00dcber
 
-jsite.about.message=<html><big><b>jSite {0}</b></big><br><br>Copyright \u00a9 2006-2010 David Roden<br>Ver\u00f6ffentlicht unter der GNU General Public License</html>
+jsite.about.message=<html><big><b>jSite {0}</b></big><br><br>Copyright \u00a9 2006\u20132012 David Roden<br>Ver\u00f6ffentlicht unter der GNU General Public License</html>
 
 jsite.node-manager.heading=Nodeverwaltung
 jsite.node-manager.description=Verwalten Sie hier Ihre Nodes.
@@ -74,6 +73,14 @@ jsite.preferences.temp-directory.default=Standard (vom System bestimmt)
 jsite.preferences.temp-directory.custom=Eigenes
 jsite.preferences.temp-directory.choose=Ausw\u00e4hlen
 jsite.preferences.temp-directory.choose.approve=Ausw\u00e4hlen
+jsite.preferences.config-directory=Lage der Konfigurationsdatei
+jsite.preferences.config-directory.jar=Neben der JAR-Datei
+jsite.preferences.config-directory.home=Benutzerverzeichnis
+jsite.preferences.config-directory.custom=Angegebenes Verzeichnis
+jsite.preferences.insert-options=Einf\u00fcgeoptionen
+jsite.preferences.insert-options.use-early-encode=Endg\u00fcltige URI fr\u00fcher berechnen
+jsite.preferences.insert-options.priority=Priorit\u00e4t
+jsite.preferences.insert-options.manifest-putter=Manifesterstellung
 
 jsite.insert.heading=Projekt einf\u00fcgen
 jsite.insert.description=Bitte warten Sie, w\u00e4hrend das Projekt eingef\u00fcgt wird.
@@ -126,7 +133,7 @@ jsite.project.project.path=Seitenpfad
 jsite.project.project.edition=Edition
 jsite.project.project.uri=Anfrage-URI
 jsite.project.keygen.io-error=<html><b>Kommunikation fehlgeschlagen</b><br><br>Die Kommunikation mit dem Freenet Node<br>ergab folgende Fehlermeldung:<br><br><code>{0}</code><br><br>Bitte vergewissern Sie sich, dass der Node l\u00e4uft und dass Sie<br> den korrekten Hostnamen und die korrekte Portnummer auf der<br>\u201eNode Einstellungen\u201c Seite eingegeben haben.</html>
-jsite.project.warning.generate-new-key=<html><b>Neues Schl\u00fcsselpaar generieren?</b><br><br>Wenn Sie das Schl\u00fcsselpaar f\u00fcr das Projekt \u00e4ndern,<br>wird sich die URI f\u00fcr Ihr Projekt ebenfalls<br>\u00e4ndern, und jegliches Vertrauen, dass andere<br>Benutzer in das alte Schl\u00fcsselpaar hatten, wird<br>verloren gehen!</html>
+jsite.project.warning.generate-new-key=<html><b>Neues Schl\u00fcsselpaar generieren?</b><br><br>Wenn Sie das Schl\u00fcsselpaar f\u00fcr das Projekt \u00e4ndern,<br>wird sich die URI f\u00fcr Ihr Projekt ebenfalls<br>\u00e4ndern, und jegliches Vertrauen, dass andere<br>Benutzer in das alte Schl\u00fcsselpaar hatten, wird<br>verloren gehen! Au\u00dferdem wird die Edition zur\u00fcckgesetzt.</html>
 jsite.project.warning.reset-edition=<html><b>Edition zur\u00fccksetzen?</b><br><br>Das Zur\u00fccksetzen der Editionsnummer kann zum<br>Fehlschlagen des Einf\u00fcgens f\u00fchren, wenn sich nicht<br>auch die URI oder der Pfad des Projekts ge\u00e4ndert haben!</html>
 jsite.project.warning.use-clipboard-now=<html><b>Anfrage-URI kopiert</b><br><br>Bitte beachten Sie, dass die Zwischenablage nach dem<br>Beenden von jSite eventuell nicht mehr die kopierte<br>URI enth\u00e4lt. Bitte f\u00fcgen Sie sie daher schleunigst in<br>ein anderes Programm ein!</html>
 
@@ -134,15 +141,6 @@ jsite.project-files.heading=Projektdateien
 jsite.project-files.description=<html>Auf dieser Seite k\u00f6nnen Parameter f\u00fcr die einzelnen Dateien dieses Projekts angegeben werden, z.B.<br>extern erstellte Schl\u00fcssel oder der korrekte MIME-Typ, wenn er nicht automatisch richtig erkannt wurde.</html>
 jsite.project-files.action.rescan=Erneut einlesen
 jsite.project-files.action.rescan.tooltip=Die Liste mit Dateien dieses Projekts neu einlesen
-jsite.project-files.action.add-container=Hinzuf\u00fcgen
-jsite.project-files.action.add-container.tooltip=Erzeugt einen neuen Container und f\u00fcgt diese Datei hinzu
-jsite.project-files.action.add-container.message=Bitte geben Sie den Namen des neuen Containers an
-jsite.project-files.action.edit-container=\u00c4ndern
-jsite.project-files.action.edit-container.tooltip=\u00c4ndert den Namen des Containers
-jsite.project-files.action.edit-container.message=Bitte geben Sie den neuen Namen des Containers an
-jsite.project-files.action.delete-container=L\u00f6schen
-jsite.project-files.action.delete-container.tooltip=L\u00f6scht diesen Container
-jsite.project-files.action.delete-container.message=Wollen Sie diesen Container wirklich l\u00f6schen?
 jsite.project-files.ignore-hidden-files=Versteckte Dateien ignorieren
 jsite.project-files.ignore-hidden-files.tooltip=Verhindert, dass versteckte Dateien hochgeladen werden
 jsite.project-files.file-options=Dateioptionen
@@ -150,6 +148,8 @@ jsite.project-files.default=Index-Datei
 jsite.project-files.default.tooltip=Lege Index-Datei f\u00fcr Projekt fest
 jsite.project-files.insert=Einf\u00fcgen
 jsite.project-files.insert.tooltip=jSite f\u00fcgt diese Datei ein
+jsite.project-files.force-insert=Einf\u00fcgen erzwingen
+jsite.project-files.force-insert.tooltip=F\u00fcgt diese Datei ein, auch wenn sie nicht modifiziert wurde
 jsite.project-files.insert-redirect=Umleitung
 jsite.project-files.insert-redirect.tooltip=F\u00fcgt eine Umleitung ein
 jsite.project-files.custom-key=Extern erstellter Schl\u00fcssel
@@ -160,12 +160,9 @@ jsite.project-files.mime-type=MIME-Typ
 jsite.project-files.mime-type.tooltip=Den richtigen MIME-Typ hier ausw\u00e4hlen, wenn die automatische Erkennenung falsch ist
 jsite.project-files.container=Container
 jsite.project-files.container.tooltip=W\u00e4hlt einen Container f\u00fcr diese Datei aus
-jsite.project-files.replacement=Ersetzungen
-jsite.project-files.replacement.tooltip=Aktiviert Ersetzungen in Datei
-jsite.project-files.replacement.edition-range=Reichweite
-jsite.project-files.replacement.edition-range.tooltip=Ersetzt auch $[EDITION+1], $[EDITION+2], usw.
 jsite.project-files.scan-error=<html><b>Fehler beim Einlesen der Dateien</b><br><br>Entweder existiert das Projektverzeichnis nicht,<br>oder einige Dateien und/oder Verzeichnisse sind nicht lesbar!<br>Bitte gehen Sie zur\u00fcck und beheben Sie den Fehler!</html>
 jsite.project-files.insert-now=Jetzt einf\u00fcgen
+jsite.project-files.invalid-default-file=Nur Dateien im obersten Verzeichnis d\u00fcrfen als Index-Dateien ausgew\u00e4hlt werden.
 
 jsite.update-checker.found-version.title=Neue Version gefunden
 jsite.update-checker.found-version.message=<html>Eine neue Version wurde gefunden.<br><br>Version {0} (ver\u00f6ffentlicht {1,date})</html>
@@ -185,7 +182,6 @@ jsite.key-dialog.label.public-key=\u00d6ffentlicher Schl\u00fcssel
 jsite.key-dialog.label.actions=<html><b>Aktionen</b></html>
 
 jsite.warning.empty-index=<html><b>Keine Index-Datei gew\u00e4hlt</b><br><br>Sie haben keine Index-Datei f\u00fcr das Projekt angegeben.<br>Obwohl es m\u00f6glich ist, das zu machen, sollten Sie doch<br>eine Index-Datei angeben, um das Browsen zu erleichtern.</html>
-jsite.warning.container-index=<html><b>Index-Datei in Container</b><br><br>Ihre Index-Datei befindet sich in einem Container! Das kann<br>dazu f\u00fchren, dass Ihre Freesite von anderen Leuten gemieden wird.</html>
 jsite.warning.index-not-html=<html><b>Index-Datei ist kein HTML</b><br><br>Ihre Index-Datei hat nicht den MIME-Typ "text/html"!<br>Das kann beim Besuch Ihrer Freesite zu<br>unerwarteten Ergebnissen f\u00fchren.</html>
 
 jsite.error.no-node-selected=<html><b>Kein Node ausgew\u00e4hlt</b><br><br>Bitte w\u00e4hlen Sie einen Node aus dem Men\u00fc!</html>
index 49fcce8..39f9acd 100644 (file)
@@ -1,6 +1,5 @@
 #
-# jSite - a tool for uploading websites into Freenet
-# Copyright (C) 2006 David Roden
+# jSite - jSite_fr.properties - 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
@@ -27,9 +26,6 @@
 # lines single quotes (ASCII 39) needs to be escaped by entering them twice,
 # otherwise the placeholder will not be replaced!
 
-jsite.main.already-running=<html><b>jSite est d\u00e9ja lanc\u00e9!</b><br><br>Ne faites pas tourner plusieurs instances<br> sous peine de perdre vos fichiers de configuration !</html>
-jsite.main.already-running.override=D\u00e9marrer
-
 jsite.general.ok=OK
 jsite.general.cancel=Annuler
 
@@ -38,6 +34,9 @@ jsite.wizard.next=Suivant
 jsite.wizard.quit=Quitter
 
 jsite.quit.question=Voulez-vous r\u00e9ellement quitter?
+jsite.quit.question.title=Souhaitez vous quitter?
+jsite.quit.overwrite-configuration=<html><b>Ecraser la configuration?</b><br><br>Un fichier de configuration éxiste déjà:<br><code>{0}</code><br><br>Doit-il être écrasé ?</html>
+jsite.quit.overwrite-configuration.title=Ecraser la configuration?
 jsite.quit.config-not-saved=<html><b>Configuration non sauvegard\u00e9e</b><br><br>La configuration n'a pas pu \u00eatre sauv\u00e9e.<br>Voulez vous quitter tout de m\u00eame?</html>
 
 jsite.menu.languages=Langue
@@ -54,7 +53,7 @@ jsite.menu.help=Aide
 jsite.menu.help.check-for-updates=Mises \u00e0 jour
 jsite.menu.help.about=A propos de jSite
 
-jsite.about.message=<html><big><b>jSite {0}</b></big><br><br>Copyright \u00a9 2006-2010 David Roden<br>Released under the GNU General Public License</html>
+jsite.about.message=<html><big><b>jSite {0}</b></big><br><br>Copyright \u00a9 2006\u20132012 David Roden<br>Publié sous GNU General Public License</html>
 
 jsite.node-manager.heading=Gestionnaire de noeud
 jsite.node-manager.description=G\u00e9rez vos noeuds.
@@ -74,6 +73,14 @@ jsite.preferences.temp-directory.default=D\u00e9faut (choix syst\u00e8mes)
 jsite.preferences.temp-directory.custom=Personnalis\u00e9
 jsite.preferences.temp-directory.choose=Choisir
 jsite.preferences.temp-directory.choose.approve=Choisir
+jsite.preferences.config-directory=Chemin du fichier de configuration
+jsite.preferences.config-directory.jar=Suivant, le fichier JAR
+jsite.preferences.config-directory.home=Acceuil
+jsite.preferences.config-directory.custom=Répertoire personnel
+jsite.preferences.insert-options=Options d'insertion
+jsite.preferences.insert-options.use-early-encode=Générer d'abord l'URI
+jsite.preferences.insert-options.priority=Priorité
+jsite.preferences.insert-options.manifest-putter=Ajout de Manifest
 
 jsite.insert.heading=Projet d'insertion
 jsite.insert.description=Veuillez attendre durant l'insertion du projet.
@@ -83,10 +90,13 @@ jsite.insert.start-time=Commenc\u00e9 \u00e0
 jsite.insert.starting=D\u00e9marrage\u2026
 jsite.insert.done=Termin\u00e9.
 jsite.insert.done.title=Insertion effectu\u00e9e
+jsite.insert.insert-aborted=L'insertion a été annulée.
+jsite.insert.insert-aborted.title=Insertion Annulée
 jsite.insert.progress=Avancement
 jsite.insert.k-per-s=Ko/s
 jsite.insert.insert-failed=<html><b>Insertion \u00e9chou\u00e9e</b><br><br>L'insertion du projet \u00e0 \u00e9chou\u00e9e.<br>Certain fichiers n'ont pas \u00e9t\u00e9 ins\u00e9r\u00e9s.</html>
 jsite.insert.insert-failed-with-cause=<html><b>Insertion \u00e9chou\u00e9e</b><br><br>L'insertion du projet \u00e0 \u00e9chou\u00e9e.<br>Certain fichiers n'ont pas \u00e9t\u00e9 ins\u00e9r\u00e9s.<br>L'erreur suivante s'est produite:<br><br><code>{0}</code></html>
+jsite.insert.insert-failed.title=Insertion Echouée
 jsite.insert.inserted=<html><b>Projet ins\u00e9r\u00e9!</b><br><br>Votre projet \u00e0 \u00e9t\u00e9 correctement ins\u00e9r\u00e9.</html>
 jsite.insert.okay-copy-uri=Copier l'URI vers le presse-papiers
 jsite.insert.reinserted-edition=<html><b>Edition r\u00e9ins\u00e9r\u00e9e</b><br><br>L'\u00e9dition que vous \u00eates en train d'ins\u00e9rer<br>a d\u00e9j\u00e0 \u00e9t\u00e9 ins\u00e9r\u00e9e avant.</html>
@@ -131,15 +141,6 @@ jsite.project-files.heading=Fichiers du projet
 jsite.project-files.description=<html>Dans cette page vous pouvez sp\u00e9cifier les informations concernant la configuration des noeuds telles que:<br>Le type de contenu mime si l'auto d\u00e9tection \u00e0 \u00e9chou\u00e9e.</html>
 jsite.project-files.action.rescan=Re-scan
 jsite.project-files.action.rescan.tooltip=V\u00e9rifier la pr\u00e9sence de nouveau fichiers
-jsite.project-files.action.add-container=Ajouter
-jsite.project-files.action.add-container.tooltip=Ajouter un nouveau container au projet
-jsite.project-files.action.add-container.message=Entrez le nom du container
-jsite.project-files.action.edit-container=Editer
-jsite.project-files.action.edit-container.tooltip=Changer le nom du container
-jsite.project-files.action.edit-container.message=Entrez le nouveau nom du container
-jsite.project-files.action.delete-container=Supprimer
-jsite.project-files.action.delete-container.tooltip=Supprimer ce container.
-jsite.project-files.action.delete-container.message=Voulez vous r\u00e9ellement supprimer ce container?
 jsite.project-files.ignore-hidden-files=Ignorer les fichiers cach\u00e9s
 jsite.project-files.ignore-hidden-files.tooltip=Si s\u00e9lectionn\u00e9, les fichiers cach\u00e9s ne sont pas ins\u00e9r\u00e9s
 jsite.project-files.file-options=Option des fichiers
@@ -147,6 +148,8 @@ jsite.project-files.default=Fichier par d\u00e9faut
 jsite.project-files.default.tooltip=Est-ce l'index?
 jsite.project-files.insert=Ins\u00e9rer
 jsite.project-files.insert.tooltip=D\u00e9cochez si vous ne voulez pas ins\u00e9rer ce fichier
+jsite.project-files.force-insert=Forcer l'insertion
+jsite.project-files.force-insert.tooltip=Forcer l'insertion de ce fichier tant qu'il n'est pas modifié
 jsite.project-files.insert-redirect=Redirection
 jsite.project-files.insert-redirect.tooltip=Cochez si vous voulez ins\u00e9rer une redirection pour ce fichier
 jsite.project-files.custom-key=Clef existante
@@ -157,12 +160,9 @@ jsite.project-files.mime-type=MIME type
 jsite.project-files.mime-type.tooltip=S\u00e9lectionez le type MIME du fichier si la d\u00e9tection \u00e0 \u00e9chou\u00e9e
 jsite.project-files.container=Container
 jsite.project-files.container.tooltip=S\u00e9lectionnez un container pour le fichier
-jsite.project-files.replacement=Remplacer
-jsite.project-files.replacement.tooltip=Activer les remplacements
-jsite.project-files.replacement.edition-range=Plage
-jsite.project-files.replacement.edition-range.tooltip=Remplacer de $[EDITION+1] \u00e0 $[EDITION+2]
 jsite.project-files.scan-error=<html><b>Erreur lors du parcours des fichiers</b><br><br>Soit le r\u00e9pertoire du projet n'existe pas,<br>ou des fichiers/r\u00e9pertoires sont inaccessibles.<br>Veuillez revenir en arri\u00e8re et s\u00e9lectionner un autre r\u00e9pertoire.</html>
 jsite.project-files.insert-now=Ins\u00e9rer
+jsite.project-files.invalid-default-file=Seulement les fichiers de la racine peuvent être selectionnés comme fichiers par defaut
 
 jsite.update-checker.found-version.title=Nouvelle version disponible
 jsite.update-checker.found-version.message=<html>Une nouvelle version est disponible.<br><br>Version {0} (publi\u00e9e le {1,date})</html>
@@ -182,7 +182,6 @@ jsite.key-dialog.label.public-key=Cl\u00e9 publique
 jsite.key-dialog.label.actions=<html><b>Actions</b></html>
 
 jsite.warning.empty-index=<html><b>Pas de fichier par d\u00e9faut</b><br><br>Avez vous sp\u00e9cifi\u00e9 un fichier par d\u00e9faut pour le projet?<br>M\u00eame s'il est possible de ne pas en sp\u00e9cifier, c'est g\u00e9n\u00e9ralement une mauvaise id\u00e9e.</html>
-jsite.warning.container-index=<html><b>Fichier principal du container</b><br><br>Votre fichier par d\u00e9faut \u00e0 \u00e9t\u00e9 plac\u00e9 dans un container!<br>Ceci peut avoir pour effet de cacher cette page aux utilisateurs.</html>
 jsite.warning.index-not-html=<html><b>Le fichier principal n'est pas un fichier HTML!</b><br><br>Votre fichier par d\u00e9faut n'est pas du type MIME "text/html"!<br>Chargez ce type de fichiers dans un navigateur peut \u00eatre dangereux.</html>
 
 jsite.error.no-node-selected=<html><b>Pas de noeud s\u00e9lectionn\u00e9</b><br><br>S\u00e9lectionnez un noeud dans le menu!</html>
index 03ddb50..65c9253 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * jSite -
- * Copyright (C) 2006 David Roden
+ * 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
@@ -26,6 +25,7 @@ import de.todesbaum.jsite.application.InsertListener;
 import de.todesbaum.jsite.application.Node;
 import de.todesbaum.jsite.application.Project;
 import de.todesbaum.jsite.application.ProjectInserter;
+import de.todesbaum.util.io.StreamCopier.ProgressListener;
 
 /**
  * Command-line interface for jSite.
@@ -68,6 +68,7 @@ public class CLI implements InsertListener {
 
                if ((args.length == 0) || args[0].equals("-h") || args[0].equals("--help")) {
                        outputWriter.println("\nParameters:\n");
+                       outputWriter.println("  --config-file=<configuration file>");
                        outputWriter.println("  --node=<node name>");
                        outputWriter.println("  --project=<project name>");
                        outputWriter.println("  --local-directory=<local directory>");
@@ -79,11 +80,19 @@ public class CLI implements InsertListener {
                        return;
                }
 
-               Configuration configuration = new Configuration();
-               if (!configuration.createLockFile()) {
-                       outputWriter.println("Lock file found!");
-                       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();
@@ -97,6 +106,10 @@ public class CLI implements InsertListener {
 
                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);
@@ -143,9 +156,11 @@ public class CLI implements InsertListener {
                        }
                }
 
+               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.");
                        }
@@ -153,6 +168,8 @@ public class CLI implements InsertListener {
 
                configuration.setProjects(projects);
                configuration.save();
+
+               System.exit(errorCode);
        }
 
        /**
@@ -201,7 +218,12 @@ public class CLI implements InsertListener {
                        return false;
                }
                projectInserter.setProject(currentProject);
-               projectInserter.start();
+               projectInserter.start(new ProgressListener() {
+
+                       public void onProgress(long copied, long length) {
+                               System.out.print("Uploaded: " + copied + " / " + length + " bytes...\r");
+                       }
+               });
                synchronized (lockObject) {
                        while (!finished) {
                                try {
@@ -229,7 +251,7 @@ public class CLI implements InsertListener {
         * {@inheritDoc}
         */
        public void projectUploadFinished(Project project) {
-               outputWriter.println("Project \"" + project.getName() + "\" has ben uploaded, starting insert...");
+               outputWriter.println("Project \"" + project.getName() + "\" has been uploaded, starting insert...");
        }
 
        /**
index e98a18d..f5001b0 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * jSite - a tool for uploading websites into Freenet
- * Copyright (C) 2006 David Roden
+ * jSite - Configuration.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
@@ -33,10 +32,15 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 import de.todesbaum.jsite.application.FileOption;
 import de.todesbaum.jsite.application.Node;
 import de.todesbaum.jsite.application.Project;
+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.io.Closer;
 import de.todesbaum.util.io.StreamCopier;
 import de.todesbaum.util.xml.SimpleXML;
@@ -49,112 +53,92 @@ import de.todesbaum.util.xml.XML;
  */
 public class Configuration {
 
-       /** The name of the file the configuration is stored to. */
-       private String filename;
-
-       /** The name of the lock file. */
-       private String lockFilename;
-
        /** The root node of the configuration. */
        private SimpleXML rootNode;
 
-       /**
-        * Creates a new configuration with the default name of the configuration
-        * file.
-        */
-       public Configuration() {
-               this(System.getProperty("user.home") + "/.jSite/config7");
-       }
+       /** The configuration locator. */
+       private final ConfigurationLocator configurationLocator;
+
+       /** Where the configuration resides. */
+       private ConfigurationLocation configurationLocation;
 
        /**
         * Creates a new configuration that is read from the given file.
         *
-        * @param filename
-        *            The name of the configuration file
+        * @param configurationLocator
+        *            The configuration locator
+        * @param configurationLocation
+        *            The configuration directory
         */
-       public Configuration(String filename) {
-               this(filename, filename + ".lock");
+       public Configuration(ConfigurationLocator configurationLocator, ConfigurationLocation configurationLocation) {
+               this.configurationLocator = configurationLocator;
+               this.configurationLocation = configurationLocation;
+               readConfiguration(configurationLocator.getFile(configurationLocation));
        }
 
-       /**
-        * Creates a new configuration that is read from the given file and uses the
-        * given lock file.
-        *
-        * @param filename
-        *            The name of the configuration file
-        * @param lockFilename
-        *            The name of the lock file
-        */
-       public Configuration(String filename, String lockFilename) {
-               this.filename = filename;
-               this.lockFilename = lockFilename;
-               readConfiguration();
-       }
+       //
+       // ACCESSORS
+       //
 
        /**
-        * Creates the directory of the configuration file.
+        * Returns the configuration locator.
         *
-        * @return <code>true</code> if the directory exists, or if it could be
-        *         created, <code>false</code> otherwise
+        * @return The configuration locator
         */
-       private boolean createConfigDirectory() {
-               File configDirectory = new File(filename).getAbsoluteFile().getParentFile();
-               return (configDirectory.exists() && configDirectory.isDirectory()) || configDirectory.mkdirs();
+       public ConfigurationLocator getConfigurationLocator() {
+               return configurationLocator;
        }
 
        /**
-        * Creates the lock file.
+        * Returns the location the configuration will be written to when calling
+        * {@link #save()}.
         *
-        * @return <code>true</code> if the lock file did not already exist and
-        *         could be created, <code>false</code> otherwise
+        * @return The location the configuration will be written to
         */
-       public boolean createLockFile() {
-               if (!createConfigDirectory()) {
-                       return false;
-               }
-               File lockFile = new File(lockFilename);
-               try {
-                       boolean fileLocked = lockFile.createNewFile();
-                       if (fileLocked) {
-                               lockFile.deleteOnExit();
-                       }
-                       return fileLocked;
-               } catch (IOException e) {
-                       /* ignore. */
-               }
-               return false;
+       public ConfigurationLocation getConfigurationDirectory() {
+               return configurationLocation;
        }
 
        /**
-        * Tells the VM to remove the lock file on program exit.
+        * Sets the location the configuration will be written to when calling
+        * {@link #save()}.
+        *
+        * @param configurationLocation
+        *            The location to write the configuration to
         */
-       public void removeLockfileOnExit() {
-               new File(lockFilename).deleteOnExit();
+       public void setConfigurationLocation(ConfigurationLocation configurationLocation) {
+               this.configurationLocation = configurationLocation;
        }
 
        /**
         * Reads the configuration from the file.
+        *
+        * @param filename
+        *            The name of the file to read the configuration from
         */
-       private void readConfiguration() {
-               File configurationFile = new File(filename);
-               if (configurationFile.exists()) {
-                       ByteArrayOutputStream fileByteOutputStream = null;
-                       FileInputStream fileInputStream = null;
-                       try {
-                               fileByteOutputStream = new ByteArrayOutputStream();
-                               fileInputStream = new FileInputStream(configurationFile);
-                               StreamCopier.copy(fileInputStream, fileByteOutputStream, configurationFile.length());
-                               fileByteOutputStream.close();
-                               byte[] fileBytes = fileByteOutputStream.toByteArray();
-                               rootNode = SimpleXML.fromDocument(XML.transformToDocument(fileBytes));
-                               return;
-                       } catch (FileNotFoundException e) {
-                               /* ignore. */
-                       } catch (IOException e) {
-                               /* ignore. */
-                       } finally {
-                               Closer.close(fileInputStream);
-                               Closer.close(fileByteOutputStream);
+       private void readConfiguration(String filename) {
+               Logger.getLogger(Configuration.class.getName()).log(Level.CONFIG, "Reading configuration from: " + filename);
+               if (filename != null) {
+                       File configurationFile = new File(filename);
+                       if (configurationFile.exists()) {
+                               ByteArrayOutputStream fileByteOutputStream = null;
+                               FileInputStream fileInputStream = null;
+                               try {
+                                       fileByteOutputStream = new ByteArrayOutputStream();
+                                       fileInputStream = new FileInputStream(configurationFile);
+                                       StreamCopier.copy(fileInputStream, fileByteOutputStream, configurationFile.length());
+                                       fileByteOutputStream.close();
+                                       byte[] fileBytes = fileByteOutputStream.toByteArray();
+                                       rootNode = SimpleXML.fromDocument(XML.transformToDocument(fileBytes));
+                                       return;
+                               } catch (FileNotFoundException e) {
+                                       /* ignore. */
+                               } catch (IOException e) {
+                                       /* ignore. */
+                               } finally {
+                                       Closer.close(fileInputStream);
+                                       Closer.close(fileByteOutputStream);
+                               }
                        }
                }
                rootNode = new SimpleXML("configuration");
@@ -167,7 +151,8 @@ public class Configuration {
         *         <code>false</code> otherwise
         */
        public boolean save() {
-               File configurationFile = new File(filename);
+               Logger.getLogger(Configuration.class.getName()).log(Level.CONFIG, "Trying to save configuration to: " + configurationLocation);
+               File configurationFile = new File(configurationLocator.getFile(configurationLocation));
                if (!configurationFile.exists()) {
                        File configurationFilePath = configurationFile.getAbsoluteFile().getParentFile();
                        if (!configurationFilePath.exists() && !configurationFilePath.mkdirs()) {
@@ -332,7 +317,11 @@ public class Configuration {
                                        Project project = new Project();
                                        projects.add(project);
                                        project.setDescription(projectNode.getNode("description").getValue(""));
-                                       project.setIndexFile(projectNode.getNode("index-file").getValue(""));
+                                       String indexFile = projectNode.getNode("index-file").getValue("");
+                                       if (indexFile.indexOf('/') > -1) {
+                                               indexFile = "";
+                                       }
+                                       project.setIndexFile(indexFile);
                                        project.setLastInsertionTime(Long.parseLong(projectNode.getNode("last-insertion-time").getValue("0")));
                                        project.setLocalPath(projectNode.getNode("local-path").getValue(""));
                                        project.setName(projectNode.getNode("name").getValue(""));
@@ -348,8 +337,26 @@ public class Configuration {
                                        } else {
                                                project.setIgnoreHiddenFiles(true);
                                        }
-                                       SimpleXML fileOptionsNode = projectNode.getNode("file-options");
+
+                                       /* load last insert hashes. */
                                        Map<String, FileOption> fileOptions = new HashMap<String, FileOption>();
+                                       SimpleXML lastInsertHashesNode = projectNode.getNode("last-insert-hashes");
+                                       if (lastInsertHashesNode != null) {
+                                               for (SimpleXML fileNode : lastInsertHashesNode.getNodes("file")) {
+                                                       String filename = fileNode.getNode("filename").getValue();
+                                                       String lastInsertHash = fileNode.getNode("last-insert-hash").getValue();
+                                                       int lastInsertEdition = Integer.valueOf(fileNode.getNode("last-insert-edition").getValue());
+                                                       String lastInsertFilename = filename;
+                                                       if (fileNode.getNode("last-insert-filename") != null) {
+                                                               lastInsertFilename = fileNode.getNode("last-insert-filename").getValue();
+                                                       }
+                                                       FileOption fileOption = project.getFileOption(filename);
+                                                       fileOption.setLastInsertHash(lastInsertHash).setLastInsertEdition(lastInsertEdition).setLastInsertFilename(lastInsertFilename);
+                                                       fileOptions.put(filename, fileOption);
+                                               }
+                                       }
+
+                                       SimpleXML fileOptionsNode = projectNode.getNode("file-options");
                                        if (fileOptionsNode != null) {
                                                SimpleXML[] fileOptionNodes = fileOptionsNode.getNodes("file-option");
                                                for (SimpleXML fileOptionNode : fileOptionNodes) {
@@ -364,11 +371,6 @@ public class Configuration {
                                                                fileOption.setChangedName(fileOptionNode.getNode("changed-name").getValue());
                                                        }
                                                        fileOption.setMimeType(fileOptionNode.getNode("mime-type").getValue(""));
-                                                       fileOption.setContainer(fileOptionNode.getNode("container").getValue());
-                                                       if (fileOptionNode.getNode("replace-edition") != null) {
-                                                               fileOption.setReplaceEdition(Boolean.parseBoolean(fileOptionNode.getNode("replace-edition").getValue()));
-                                                               fileOption.setEditionRange(Integer.parseInt(fileOptionNode.getNode("edition-range").getValue()));
-                                                       }
                                                        fileOptions.put(filename, fileOption);
                                                }
                                        }
@@ -401,6 +403,20 @@ public class Configuration {
                        projectNode.append("insert-uri", project.getInsertURI());
                        projectNode.append("request-uri", project.getRequestURI());
                        projectNode.append("ignore-hidden-files", String.valueOf(project.isIgnoreHiddenFiles()));
+
+                       /* store last insert hashes. */
+                       SimpleXML lastInsertHashesNode = projectNode.append("last-insert-hashes");
+                       for (Entry<String, FileOption> fileOption : project.getFileOptions().entrySet()) {
+                               if ((fileOption.getValue().getLastInsertHash() == null) || (fileOption.getValue().getLastInsertHash().length() == 0)) {
+                                       continue;
+                               }
+                               SimpleXML fileNode = lastInsertHashesNode.append("file");
+                               fileNode.append("filename", fileOption.getKey());
+                               fileNode.append("last-insert-hash", fileOption.getValue().getLastInsertHash());
+                               fileNode.append("last-insert-edition", String.valueOf(fileOption.getValue().getLastInsertEdition()));
+                               fileNode.append("last-insert-filename", fileOption.getValue().getLastInsertFilename());
+                       }
+
                        SimpleXML fileOptionsNode = projectNode.append("file-options");
                        Iterator<Entry<String, FileOption>> entries = project.getFileOptions().entrySet().iterator();
                        while (entries.hasNext()) {
@@ -414,9 +430,6 @@ public class Configuration {
                                        fileOptionNode.append("custom-key", fileOption.getCustomKey());
                                        fileOptionNode.append("changed-name", fileOption.getChangedName());
                                        fileOptionNode.append("mime-type", fileOption.getMimeType());
-                                       fileOptionNode.append("container", fileOption.getContainer());
-                                       fileOptionNode.append("replace-edition", String.valueOf(fileOption.getReplaceEdition()));
-                                       fileOptionNode.append("edition-range", String.valueOf(fileOption.getEditionRange()));
                                }
                        }
                }
@@ -564,4 +577,69 @@ public class Configuration {
                }
        }
 
+       /**
+        * 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 getNodeBooleanValue(new String[] { "use-early-encode" }, false);
+       }
+
+       /**
+        * 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
+        * @return This configuration
+        */
+       public Configuration setUseEarlyEncode(boolean useEarlyEncode) {
+               rootNode.replace("use-early-encode", String.valueOf(useEarlyEncode));
+               return this;
+       }
+
+       /**
+        * Returns the insert priority.
+        *
+        * @return The insert priority
+        */
+       public PriorityClass getPriority() {
+               return PriorityClass.valueOf(getNodeValue(new String[] { "insert-priority" }, "interactive"));
+       }
+
+       /**
+        * Sets the insert priority.
+        *
+        * @param priority
+        *            The insert priority
+        * @return This configuration
+        */
+       public Configuration setPriority(PriorityClass priority) {
+               rootNode.replace("insert-priority", priority.toString());
+               return this;
+       }
+
+       /**
+        * Returns the manifest putter.
+        *
+        * @return The manifest putter
+        */
+       public ManifestPutter getManifestPutter() {
+               return ManifestPutter.valueOf(getNodeValue(new String[] { "manifest-putter" }, "simple").toUpperCase());
+       }
+
+       /**
+        * Sets the manifest putter.
+        *
+        * @param manifestPutter
+        *            The manifest putter
+        * @return This configuration
+        */
+       public Configuration setManifestPutter(ManifestPutter manifestPutter) {
+               rootNode.replace("manifest-putter", manifestPutter.name().toLowerCase());
+               return this;
+       }
+
 }
diff --git a/src/de/todesbaum/jsite/main/ConfigurationLocator.java b/src/de/todesbaum/jsite/main/ConfigurationLocator.java
new file mode 100644 (file)
index 0000000..967f936
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ * jSite - ConfigurationLocator.java - Copyright © 2011–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.File;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Locator for configuration files in different places.
+ *
+ * @author David ‘Bombe’ Roden &lt;bombe@freenetproject.org&gt;
+ */
+public class ConfigurationLocator {
+
+       /**
+        * The location of the configuration directory.
+        *
+        * @author David ‘Bombe’ Roden &lt;bombe@freenetproject.org&gt;
+        */
+       public enum ConfigurationLocation {
+
+               /** The configuration is in the same directory as the JAR file. */
+               NEXT_TO_JAR_FILE,
+
+               /**
+                * The configuration is in the user’s home directory. This is the
+                * pre-0.9.3 default.
+                */
+               HOME_DIRECTORY,
+
+               /** Custom location. */
+               CUSTOM,
+
+       }
+
+       /** The possible configuration locations. */
+       private final Map<ConfigurationLocation, String> configurationFiles = new HashMap<ConfigurationLocation, String>();
+
+       /**
+        * Creates a new configuration locator. If this class is loaded from a JAR
+        * file, {@link ConfigurationLocation#NEXT_TO_JAR_FILE} is added to the list
+        * of possible configuration file locations.
+        * {@link ConfigurationLocation#HOME_DIRECTORY} is always added to this
+        * list, {@link ConfigurationLocation#CUSTOM} has to be enabled by calling
+        * {@link #setCustomLocation(String)}.
+        */
+       public ConfigurationLocator() {
+               /* are we executed from a JAR file? */
+               String resource = getClass().getResource("/" + getClass().getName().replace(".", "/") + ".class").toString();
+               if (resource.startsWith("jar:")) {
+                       String jarFileLocation = resource.substring(9, resource.indexOf(".jar!") + 4);
+                       String jarFileDirectory = new File(jarFileLocation).getParent();
+                       File configurationFile = new File(jarFileDirectory, "jSite.conf");
+                       configurationFiles.put(ConfigurationLocation.NEXT_TO_JAR_FILE, configurationFile.getPath());
+               }
+               File homeDirectoryFile = new File(System.getProperty("user.home"), ".jSite/config7");
+               configurationFiles.put(ConfigurationLocation.HOME_DIRECTORY, homeDirectoryFile.getPath());
+       }
+
+       //
+       // ACCESSORS
+       //
+
+       /**
+        * Sets the location of the custom configuration file.
+        *
+        * @param customFile
+        *            The custom location of the configuration file
+        */
+       public void setCustomLocation(String customFile) {
+               configurationFiles.put(ConfigurationLocation.CUSTOM, customFile);
+       }
+
+       /**
+        * Returns whether the given location is valid. Certain locations (such as
+        * {@link ConfigurationLocation#NEXT_TO_JAR_FILE}) may be invalid in certain
+        * circumstances (such as the application not being run from a JAR file). A
+        * location being valid does not imply that a configuration file does exist
+        * at the given location, use {@link #hasFile(ConfigurationLocation)} to
+        * check for a configuration file at the desired location.
+        *
+        * @param configurationLocation
+        *            The configuration location
+        * @return {@code true} if the location is valid, {@code false} otherwise
+        */
+       public boolean isValidLocation(ConfigurationLocation configurationLocation) {
+               return configurationFiles.containsKey(configurationLocation);
+       }
+
+       /**
+        * Checks whether a configuration file exists at the given location.
+        *
+        * @param configurationLocation
+        *            The configuration location
+        * @return {@code true} if a configuration file exists at the given
+        *         location, {@code false} otherwise
+        */
+       public boolean hasFile(ConfigurationLocation configurationLocation) {
+               if (!isValidLocation(configurationLocation)) {
+                       return false;
+               }
+               return new File(configurationFiles.get(configurationLocation)).exists();
+       }
+
+       /**
+        * Returns the configuration file for the given location.
+        *
+        * @param configurationLocation
+        *            The location to get the file for
+        * @return The name of the configuration file at the given location, or
+        *         {@code null} if the given location is invalid
+        */
+       public String getFile(ConfigurationLocation configurationLocation) {
+               return configurationFiles.get(configurationLocation);
+       }
+
+       //
+       // ACTIONS
+       //
+
+       /**
+        * Finds the preferred location of the configuration file.
+        *
+        * @see #findPreferredLocation(ConfigurationLocation)
+        * @return The preferred location of the configuration file
+        */
+       public ConfigurationLocation findPreferredLocation() {
+               return findPreferredLocation(ConfigurationLocation.NEXT_TO_JAR_FILE);
+       }
+
+       /**
+        * Finds the preferred location of the configuration file. The following
+        * checks are performed: if a custom configuration location has been defined
+        * (by calling {@link #setCustomLocation(String)})
+        * {@link ConfigurationLocation#CUSTOM} is returned. If the application is
+        * run from a JAR file and a configuration file is found next to the JAR
+        * file (i.e. in the same directory),
+        * {@link ConfigurationLocation#NEXT_TO_JAR_FILE} is returned. If a
+        * configuration file exists in the user’s home directory,
+        * {@link ConfigurationLocation#HOME_DIRECTORY} is returned. Otherwise, the
+        * given {@code defaultLocation} is returned.
+        *
+        * @param defaultLocation
+        *            The default location to return if no other configuration file
+        *            is found
+        * @return The configuration location to load the configuration from
+        */
+       public ConfigurationLocation findPreferredLocation(ConfigurationLocation defaultLocation) {
+               if (hasFile(ConfigurationLocation.CUSTOM)) {
+                       return ConfigurationLocation.CUSTOM;
+               }
+               if (hasFile(ConfigurationLocation.NEXT_TO_JAR_FILE)) {
+                       return ConfigurationLocation.NEXT_TO_JAR_FILE;
+               }
+               if (hasFile(ConfigurationLocation.HOME_DIRECTORY)) {
+                       return ConfigurationLocation.HOME_DIRECTORY;
+               }
+               if (isValidLocation(defaultLocation)) {
+                       return defaultLocation;
+               }
+               return ConfigurationLocation.HOME_DIRECTORY;
+       }
+
+}
index af921cf..edbf5e4 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * jSite - a tool for uploading websites into Freenet
- * Copyright (C) 2006-2009 David Roden
+ * 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
@@ -19,6 +18,7 @@
 
 package de.todesbaum.jsite.main;
 
+import java.awt.Component;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.io.IOException;
@@ -39,6 +39,7 @@ 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;
@@ -49,10 +50,10 @@ 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.UpdateChecker;
-import de.todesbaum.jsite.application.UpdateListener;
 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;
@@ -61,6 +62,7 @@ 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.image.IconLoader;
 import de.todesbaum.util.swing.TWizard;
 import de.todesbaum.util.swing.TWizardPage;
@@ -77,7 +79,7 @@ public class Main implements ActionListener, ListSelectionListener, WizardListen
        private static final Logger logger = Logger.getLogger(Main.class.getName());
 
        /** The version. */
-       private static final Version VERSION = new Version(0, 9, 2);
+       private static final Version VERSION = new Version(0, 10);
 
        /** The configuration. */
        private Configuration configuration;
@@ -145,6 +147,9 @@ public class Main implements ActionListener, ListSelectionListener, WizardListen
        /** Mapping from page type to page. */
        private final Map<PageType, TWizardPage> pages = new HashMap<PageType, TWizardPage>();
 
+       /** The original location of the configuration file. */
+       private ConfigurationLocation originalLocation;
+
        /**
         * Creates a new core with the default configuration file.
         */
@@ -159,20 +164,18 @@ public class Main implements ActionListener, ListSelectionListener, WizardListen
         *            The name of the configuration file
         */
        private Main(String configFilename) {
+               /* collect all possible configuration file locations. */
+               ConfigurationLocator configurationLocator = new ConfigurationLocator();
                if (configFilename != null) {
-                       configuration = new Configuration(configFilename);
-               } else {
-                       configuration = new Configuration();
+                       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());
-               if (!configuration.createLockFile()) {
-                       int option = JOptionPane.showOptionDialog(null, I18n.getMessage("jsite.main.already-running"), "", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, new Object[] { I18n.getMessage("jsite.main.already-running.override"), I18n.getMessage("jsite.wizard.quit") }, I18n.getMessage("jsite.wizard.quit"));
-                       if (option != 0) {
-                               throw new IllegalStateException("Lockfile override not active, refusing start.");
-                       }
-                       configuration.removeLockfileOnExit();
-               }
                wizard = new TWizard();
                createActions();
                wizard.setJMenuBar(createMenuBar());
@@ -356,6 +359,17 @@ public class Main implements ActionListener, ListSelectionListener, WizardListen
        }
 
        /**
+        * 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 <code>true</code> if the configuration could be saved,
@@ -444,6 +458,12 @@ public class Main implements ActionListener, ListSelectionListener, WizardListen
         * 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);
@@ -536,6 +556,9 @@ public class Main implements ActionListener, ListSelectionListener, WizardListen
                        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);
@@ -549,8 +572,13 @@ public class Main implements ActionListener, ListSelectionListener, WizardListen
                                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());
                }
        }
 
@@ -576,9 +604,23 @@ public class Main implements ActionListener, ListSelectionListener, WizardListen
                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"), null, JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE) == JOptionPane.OK_OPTION) {
-                       if (saveConfiguration()) {
-                               System.exit(0);
+               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);
@@ -617,6 +659,25 @@ public class Main implements ActionListener, ListSelectionListener, WizardListen
        /**
         * {@inheritDoc}
         */
+       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}
+        */
        public void actionPerformed(ActionEvent e) {
                Object source = e.getSource();
                if (source instanceof JRadioButtonMenuItem) {
index 2b80c36..977836c 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * jSite - a tool for uploading websites into Freenet
- * Copyright (C) 2006 David Roden
+ * 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
index 9376159..ede92d1 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * todesbaum-lib -
- * Copyright (C) 2006 David Roden
+ * jSite - Client.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
@@ -23,6 +22,8 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
+import de.todesbaum.util.io.StreamCopier.ProgressListener;
+
 /**
  * A Client executes {@link Command}s over a {@link Connection} to a
  * {@link Node} and delivers resulting {@link Message}s.
@@ -113,6 +114,23 @@ public class Client implements ConnectionListener {
        }
 
        /**
+        * Executes the specified command. This will also clear the queue of
+        * messages, discarding all messages that resulted from the previous
+        * command and have not yet been read.
+        *
+        * @param command
+        *            The command to execute
+        * @param progressListener
+        *            The progress listener for payload transfers
+        * @throws IOException
+        *             if an I/O error occurs
+        * @see #execute(Command, boolean)
+        */
+       public void execute(Command command, ProgressListener progressListener) throws IOException {
+               execute(command, true, progressListener);
+       }
+
+       /**
         * Executes the specified command and optionally clears the list of
         * identifiers this clients listens to before starting the command.
         *
@@ -125,6 +143,24 @@ public class Client implements ConnectionListener {
         *             if an I/O error occurs
         */
        public void execute(Command command, boolean removeExistingIdentifiers) throws IOException {
+               execute(command, removeExistingIdentifiers, null);
+       }
+
+       /**
+        * Executes the specified command and optionally clears the list of
+        * identifiers this clients listens to before starting the command.
+        *
+        * @param command
+        *            The command to execute
+        * @param removeExistingIdentifiers
+        *            If <code>true</code>, the list of identifiers that this
+        *            clients listens to is cleared
+        * @param progressListener
+        *            The progress listener for payload transfers
+        * @throws IOException
+        *             if an I/O error occurs
+        */
+       public void execute(Command command, boolean removeExistingIdentifiers, ProgressListener progressListener) throws IOException {
                synchronized (messageQueue) {
                        messageQueue.clear();
                        if (removeExistingIdentifiers) {
@@ -132,7 +168,7 @@ public class Client implements ConnectionListener {
                        }
                        identifiers.add(command.getIdentifier());
                }
-               connection.execute(command);
+               connection.execute(command, progressListener);
        }
 
        /**
index d6fb70f..5b340e4 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * jSite-remote - ClientGet.java -
- * Copyright © 2008 David Roden
+ * jSite - ClientGet.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
index 4b9e41d..5983078 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * todesbaum-lib -
- * Copyright (C) 2006 David Roden
+ * jSite - ClientHello.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
index acaad49..9993173 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * todesbaum-lib -
- * Copyright (C) 2006 David Roden
+ * jSite - ClientPut.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
index 4be7528..d72f2c1 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * todesbaum-lib -
- * Copyright (C) 2006 David Roden
+ * jSite - ClientPutComplexDir.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
@@ -38,7 +37,7 @@ import de.todesbaum.util.io.Closer;
  * @author David Roden &lt;droden@gmail.com&gt;
  * @version $Id$
  */
-public class ClientPutComplexDir extends ClientPutDir {
+public class ClientPutComplexDir extends ClientPutDir<ClientPutComplexDir> {
 
        /** The file entries of this directory. */
        private List<FileEntry> fileEntries = new ArrayList<FileEntry>();
index b9291a9..386fe1d 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * todesbaum-lib -
- * Copyright (C) 2006 David Roden
+ * jSite - ClientPutDir.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
@@ -25,14 +24,72 @@ import java.io.Writer;
 /**
  * Abstract base class for all put requests that insert a directory.
  *
+ * @param <C>
+ *            The type of the “ClientPutDir” command
  * @author David Roden &lt;droden@gmail.com&gt;
- * @version $Id$
  */
-public class ClientPutDir extends ClientPut {
+public class ClientPutDir<C extends ClientPutDir<?>> extends ClientPut {
+
+       /**
+        * All possible manifest putters. Manifest putters are used to distribute
+        * files of a directory insert to different containers, depending on size,
+        * type, and other factors.
+        *
+        * @author David ‘Bombe’ Roden &lt;bombe@freenetproject.org&gt;
+        */
+       public enum ManifestPutter {
+
+               /**
+                * Use the “simple” manifest putter. Despite its name this is currently
+                * the default manifest putter.
+                */
+               SIMPLE("simple"),
+
+               /** Use the “default” manifest putter. */
+               DEFAULT("default");
+
+               /** The name of the manifest putter. */
+               private final String name;
+
+               /**
+                * Creates a new manifest putter.
+                *
+                * @param name
+                *            The name of the manifest putter
+                */
+               private ManifestPutter(String name) {
+                       this.name = name;
+               }
+
+               /**
+                * Returns the name of the manifest putter.
+                *
+                * @return The name of the manifest putter
+                */
+               public String getName() {
+                       return name;
+               }
+
+               //
+               // OBJECT METHODS
+               //
+
+               /**
+                * {@inheritDoc}
+                */
+               @Override
+               public String toString() {
+                       return name.substring(0, 1).toUpperCase() + name.substring(1);
+               }
+
+       }
 
        /** The default file of the directory. */
        protected String defaultName;
 
+       /** The manifest putter to use. */
+       private ManifestPutter manifestPutter;
+
        /**
         * Creates a new request with the specified name, identifier, and URI.
         *
@@ -71,6 +128,30 @@ public class ClientPutDir extends ClientPut {
        }
 
        /**
+        * Returns the current manifest putter.
+        *
+        * @return The current manifest putter (may be {@code null})
+        */
+       public ManifestPutter getManifestPutter() {
+               return manifestPutter;
+       }
+
+       /**
+        * Sets the manifest putter for the “ClientPutDir” command. If {@code null}
+        * is given the node will choose a manifest putter.
+        *
+        * @param manifestPutter
+        *            The manifest putter to use for the command (may be
+        *            {@code null})
+        * @return This ClientPutDir command
+        */
+       @SuppressWarnings("unchecked")
+       public C setManifestPutter(ManifestPutter manifestPutter) {
+               this.manifestPutter = manifestPutter;
+               return (C) this;
+       }
+
+       /**
         * {@inheritDoc}
         */
        @Override
@@ -78,6 +159,9 @@ public class ClientPutDir extends ClientPut {
                super.write(writer);
                if (defaultName != null)
                        writer.write("DefaultName=" + defaultName + LINEFEED);
+               if (manifestPutter != null) {
+                       writer.write("ManifestPutter=" + manifestPutter.getName() + LINEFEED);
+               }
        }
 
 }
index 9fc9527..63aaa9c 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * todesbaum-lib -
- * Copyright (C) 2006 David Roden
+ * jSite - Command.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
index 3fd39f6..c4cb669 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * todesbaum-lib -
- * Copyright (C) 2006 David Roden
+ * jSite - Connection.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
@@ -34,6 +33,7 @@ import java.util.List;
 import de.todesbaum.util.io.Closer;
 import de.todesbaum.util.io.LineInputStream;
 import de.todesbaum.util.io.StreamCopier;
+import de.todesbaum.util.io.StreamCopier.ProgressListener;
 import de.todesbaum.util.io.TempFileInputStream;
 
 /**
@@ -215,34 +215,15 @@ public class Connection {
         * Disconnects from the node.
         */
        public void disconnect() {
-               if (nodeWriter != null) {
-                       try {
-                               nodeWriter.close();
-                       } catch (IOException ioe1) {
-                       }
-                       nodeWriter = null;
-               }
-               if (nodeOutputStream != null) {
-                       try {
-                               nodeOutputStream.close();
-                       } catch (IOException ioe1) {
-                       }
-                       nodeOutputStream = null;
-               }
-               if (nodeInputStream != null) {
-                       try {
-                               nodeInputStream.close();
-                       } catch (IOException ioe1) {
-                       }
-                       nodeInputStream = null;
-               }
-               if (nodeSocket != null) {
-                       try {
-                               nodeSocket.close();
-                       } catch (IOException ioe1) {
-                       }
-                       nodeSocket = null;
-               }
+               Closer.close(nodeWriter);
+               nodeWriter = null;
+               Closer.close(nodeOutputStream);
+               nodeOutputStream = null;
+               Closer.close(nodeInputStream);
+               nodeInputStream = null;
+               nodeInputStream = null;
+               Closer.close(nodeSocket);
+               nodeSocket = null;
                synchronized (this) {
                        notify();
                }
@@ -260,6 +241,22 @@ public class Connection {
         *             if an I/O error occurs
         */
        public synchronized void execute(Command command) throws IllegalStateException, IOException {
+               execute(command, null);
+       }
+
+       /**
+        * Executes the specified command.
+        *
+        * @param command
+        *            The command to execute
+        * @param progressListener
+        *            A progress listener for a payload transfer
+        * @throws IllegalStateException
+        *             if the connection is not connected
+        * @throws IOException
+        *             if an I/O error occurs
+        */
+       public synchronized void execute(Command command, ProgressListener progressListener) throws IllegalStateException, IOException {
                if (nodeSocket == null) {
                        throw new IllegalStateException("connection is not connected");
                }
@@ -271,7 +268,7 @@ public class Connection {
                        InputStream payloadInputStream = null;
                        try {
                                payloadInputStream = command.getPayload();
-                               StreamCopier.copy(payloadInputStream, nodeOutputStream, command.getPayloadLength());
+                               StreamCopier.copy(payloadInputStream, nodeOutputStream, command.getPayloadLength(), progressListener);
                        } finally {
                                Closer.close(payloadInputStream);
                        }
@@ -307,6 +304,7 @@ public class Connection {
                 * Main loop of the reader. Lines are read and converted into
                 * {@link Message} objects.
                 */
+               @SuppressWarnings("synthetic-access")
                public void run() {
                        LineInputStream nodeReader = null;
                        try {
index 9ce101d..8e55a2d 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * todesbaum-lib -
- * Copyright (C) 2006 David Roden
+ * jSite - ConnectionListener.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
index c697c1f..9b12e74 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * todesbaum-lib -
- * Copyright (C) 2006 David Roden
+ * jSite - DirectFileEntry.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
index bb34023..bf036b4 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * todesbaum-lib -
- * Copyright (C) 2006 David Roden
+ * jSite - DiskFileEntry.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
index 3fcdf8b..7adb11f 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * todesbaum-lib -
- * Copyright (C) 2006 David Roden
+ * jSite - FileEntry.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
index d21b7b9..84e4bf6 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * todesbaum-lib -
- * Copyright (C) 2006 David Roden
+ * jSite - GenerateSSK.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
index 4810e76..6c934ec 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * todesbaum-lib -
- * Copyright (C) 2006 David Roden
+ * jSite - Message.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
@@ -22,8 +21,8 @@ package de.todesbaum.util.freenet.fcp2;
 import java.io.InputStream;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Set;
 import java.util.Map.Entry;
+import java.util.Set;
 
 /**
  * Contains replies sent by the Freenet node. A message always has a name, and
index ee50ca9..d2fa91c 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * todesbaum-lib -
- * Copyright (C) 2006 David Roden
+ * jSite - Node.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
index 6f6cdca..55e73f8 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * todesbaum-lib -
- * Copyright (C) 2006 David Roden
+ * jSite - Persistence.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
index 8a8390e..623bc5b 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * todesbaum-lib -
- * Copyright (C) 2006 David Roden
+ * jSite - PriorityClass.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
@@ -90,4 +89,37 @@ public final class PriorityClass {
                return value;
        }
 
+       //
+       // STATIC METHODS
+       //
+
+       /**
+        * Returns the priority class with the given name, matched case-insensitive.
+        *
+        * @param value
+        *            The name of the priority
+        * @return The priority with the given name, or {@code null} if no priority
+        *         matches the given name
+        */
+       public static PriorityClass valueOf(String value) {
+               for (PriorityClass priorityClass : new PriorityClass[] { MINIMUM, PREFETCH, BULK, UPDATABLE, SEMI_INTERACTIVE, INTERACTIVE, MAXIMUM }) {
+                       if (priorityClass.getName().equalsIgnoreCase(value)) {
+                               return priorityClass;
+                       }
+               }
+               return null;
+       }
+
+       //
+       // OBJECT METHODS
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public String toString() {
+               return name;
+       }
+
 }
index c4354c6..06a2171 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * todesbaum-lib -
- * Copyright (C) 2006 David Roden
+ * jSite - RedirectFileEntry.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
index 14e2482..6f2ed7a 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * jSite-remote - ReturnType.java -
- * Copyright © 2008 David Roden
+ * jSite - ReturnType.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
index e64540f..e59b6a3 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * todesbaum-lib -
- * Copyright (C) 2006 David Roden
+ * jSite - Verbosity.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
index d764991..ff6441a 100644 (file)
@@ -1,4 +1,6 @@
 /*
+ * jSite - IconLoader.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
index ea99081..67f7c57 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * todesbaum-lib - Copyright (C) 2006 David Roden
+ * jSite - Closer.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
index a49e876..4fe0d07 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * todesbaum-lib -
- * Copyright (C) 2006 David Roden
+ * jSite - LineInputStream.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
diff --git a/src/de/todesbaum/util/io/ReplacingOutputStream.java b/src/de/todesbaum/util/io/ReplacingOutputStream.java
deleted file mode 100644 (file)
index 256714b..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * todesbaum-lib -
- * Copyright (C) 2006 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.util.io;
-
-import java.io.FilterOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Map.Entry;
-
-
-/**
- * @author David Roden &lt;droden@gmail.com&gt;
- * @version $Id$
- */
-public class ReplacingOutputStream extends FilterOutputStream {
-
-       private Map<String, String> replacements = new HashMap<String, String>();
-       private StringBuffer ringBuffer = new StringBuffer();
-
-       /**
-        * @param out
-        */
-       public ReplacingOutputStream(OutputStream out) {
-               super(out);
-       }
-
-       public void addReplacement(String token, String value) {
-               replacements.put(token, value);
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public void write(int b) throws IOException {
-               ringBuffer.append((char) b);
-               Iterator<Entry<String, String>> entries = replacements.entrySet().iterator();
-               boolean found = false;
-               Entry<String, String> entry = null;
-               while (!found && entries.hasNext()) {
-                       entry = entries.next();
-                       if (entry.getKey().startsWith(ringBuffer.toString())) {
-                               found = true;
-                       }
-               }
-               if (!found) {
-                       String buffer = ringBuffer.toString();
-                       for (int index = 0, size = buffer.length(); index < size; index++) {
-                               super.write(buffer.charAt(index));
-                       }
-                       ringBuffer.setLength(0);
-               } else {
-                       if (entry.getKey().equals(ringBuffer.toString())) {
-                               String buffer = entry.getValue();
-                               for (int index = 0, size = buffer.length(); index < size; index++) {
-                                       super.write(buffer.charAt(index));
-                               }
-                               ringBuffer.setLength(0);
-                       }
-               }
-       }
-
-}
index dccd09a..c7d99de 100644 (file)
@@ -1,4 +1,6 @@
 /*
+ * jSite - StreamCopier.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
@@ -20,6 +22,7 @@ import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.EventListener;
 
 /**
  * Copies input from an {@link InputStream} to an {@link OutputStream}.
@@ -105,6 +108,23 @@ public class StreamCopier {
        }
 
        /**
+        * Copies the stream data. If the input stream is depleted before the
+        * requested number of bytes have been read an {@link EOFException} is
+        * thrown.
+        *
+        * @param progressListener
+        *            The progress listener (may be {@code null})
+        * @throws EOFException
+        *             if the input stream is depleted before the requested number
+        *             of bytes has been read
+        * @throws IOException
+        *             if an I/O error occurs
+        */
+       public void copy(ProgressListener progressListener) throws EOFException, IOException {
+               copy(inputStream, outputStream, length, bufferSize, progressListener);
+       }
+
+       /**
         * Copies <code>length</code> bytes from the <code>inputStream</code> to
         * the <code>outputStream</code>.
         *
@@ -123,6 +143,25 @@ public class StreamCopier {
 
        /**
         * Copies <code>length</code> bytes from the <code>inputStream</code> to
+        * the <code>outputStream</code>.
+        *
+        * @param inputStream
+        *            The input stream to read from
+        * @param outputStream
+        *            The output stream to write to
+        * @param length
+        *            The number of bytes to copy
+        * @param progressListener
+        *            The progress listener (may be {@code null})
+        * @throws IOException
+        *             if an I/O exception occurs
+        */
+       public static void copy(InputStream inputStream, OutputStream outputStream, long length, ProgressListener progressListener) throws IOException {
+               copy(inputStream, outputStream, length, BUFFER_SIZE, progressListener);
+       }
+
+       /**
+        * Copies <code>length</code> bytes from the <code>inputStream</code> to
         * the <code>outputStream</code> using a buffer with the specified size
         *
         * @param inputStream
@@ -137,6 +176,27 @@ public class StreamCopier {
         *             if an I/O exception occurs
         */
        public static void copy(InputStream inputStream, OutputStream outputStream, long length, int bufferSize) throws IOException {
+               copy(inputStream, outputStream, length, bufferSize, null);
+       }
+
+       /**
+        * Copies <code>length</code> bytes from the <code>inputStream</code> to
+        * the <code>outputStream</code> using a buffer with the specified size
+        *
+        * @param inputStream
+        *            The input stream to read from
+        * @param outputStream
+        *            The output stream to write to
+        * @param length
+        *            The number of bytes to copy
+        * @param bufferSize
+        *            The size of the copy buffer
+        * @param progressListener
+        *            The progress listener (may be {@code null})
+        * @throws IOException
+        *             if an I/O exception occurs
+        */
+       public static void copy(InputStream inputStream, OutputStream outputStream, long length, int bufferSize, ProgressListener progressListener) throws IOException {
                long remaining = length;
                byte[] buffer = new byte[bufferSize];
                while (remaining > 0) {
@@ -146,7 +206,30 @@ public class StreamCopier {
                        }
                        outputStream.write(buffer, 0, read);
                        remaining -= read;
+                       if (progressListener != null) {
+                               progressListener.onProgress(length - remaining, length);
+                       }
                }
        }
 
+       /**
+        * Interface for objects that want to be notified about the progress of a
+        * {@link StreamCopier#copy()} operation.
+        *
+        * @author David ‘Bombe’ Roden &lt;bombe@freenetproject.org&gt;
+        */
+       public static interface ProgressListener extends EventListener {
+
+               /**
+                * Notifiies a listener that a copy process made some progress.
+                *
+                * @param copied
+                *            The number of bytes that have already been copied
+                * @param length
+                *            The total number of bytes that will be copied
+                */
+               public void onProgress(long copied, long length);
+
+       }
+
 }
diff --git a/src/de/todesbaum/util/io/TeeOutputStream.java b/src/de/todesbaum/util/io/TeeOutputStream.java
new file mode 100644 (file)
index 0000000..78860dc
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * jSite - TeeOutputStream.java - Copyright © 2010 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+package de.todesbaum.util.io;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * {@link OutputStream} that sends all data it receives to multiple other output
+ * streams. If an error occurs during a {@link #write(int)} to one of the
+ * underlying output streams no guarantees are made about how much data is sent
+ * to each stream, i.e. there is no good way to recover from such an error.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class TeeOutputStream extends OutputStream {
+
+       /** The output streams. */
+       private final OutputStream[] outputStreams;
+
+       /**
+        * Creates a new tee output stream that sends all to all given output
+        * streams.
+        *
+        * @param outputStreams
+        *            The output streams to send all data to
+        */
+       public TeeOutputStream(OutputStream... outputStreams) {
+               this.outputStreams = outputStreams;
+       }
+
+       /**
+        * {@inheritDoc}
+        * <p>
+        * An effort is made to close all output streams. If multiple exceptions
+        * occur, only the first exception is thrown after all output streams have
+        * been tried to close.
+        */
+       @Override
+       public void close() throws IOException {
+               IOException occuredException = null;
+               for (OutputStream outputStream : outputStreams) {
+                       try {
+                               outputStream.flush();
+                       } catch (IOException ioe1) {
+                               if (occuredException == null) {
+                                       occuredException = ioe1;
+                               }
+                       }
+               }
+               if (occuredException != null) {
+                       throw occuredException;
+               }
+       }
+
+       /**
+        * {@inheritDoc}
+        * <p>
+        * An effort is made to flush all output streams. If multiple exceptions
+        * occur, only the first exception is thrown after all output streams have
+        * been tried to flush.
+        */
+       @Override
+       public void flush() throws IOException {
+               IOException occuredException = null;
+               for (OutputStream outputStream : outputStreams) {
+                       try {
+                               outputStream.flush();
+                       } catch (IOException ioe1) {
+                               if (occuredException == null) {
+                                       occuredException = ioe1;
+                               }
+                       }
+               }
+               if (occuredException != null) {
+                       throw occuredException;
+               }
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void write(byte[] buffer) throws IOException {
+               for (OutputStream outputStream : outputStreams) {
+                       outputStream.write(buffer);
+               }
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void write(byte[] buffer, int offset, int length) throws IOException {
+               for (OutputStream outputStream : outputStreams) {
+                       outputStream.write(buffer, offset, length);
+               }
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void write(int data) throws IOException {
+               for (OutputStream outputStream : outputStreams) {
+                       outputStream.write(data);
+               }
+       }
+
+}
index 5ebeb9b..43e2047 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * todesbaum-lib -
- * Copyright (C) 2006 David Roden
+ * jSite - TempFileInputStream.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
index 683368c..39a5ce2 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * todesbaum-lib -
- * Copyright (C) 2006 David Roden
+ * jSite - SortedListModel.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
index 6515627..0393f5c 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * todesbaum-lib -
- * Copyright (C) 2006 David Roden
+ * jSite - TLabel.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
index 0ccffbc..464ac69 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * todesbaum-lib -
- * Copyright (C) 2006 David Roden
+ * jSite - TWizard.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
index be51b7e..a10b475 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * todesbaum-lib -
- * Copyright (C) 2006 David Roden
+ * jSite - TWizardPage.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
index f0ab6b0..987075d 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * todesbaum-lib -
- * Copyright (C) 2006 David Roden
+ * jSite - WizardListener.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
index b41d600..8351581 100644 (file)
@@ -1,4 +1,6 @@
 /*
+ * jSite - SimpleXML.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
index 28f8298..8b6b259 100644 (file)
@@ -1,4 +1,6 @@
 /*
+ * jSite - XML.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