Merge branch 'release/0.9.3' 0.9.3
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Wed, 22 Jul 2015 18:57:58 +0000 (20:57 +0200)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Wed, 22 Jul 2015 18:57:58 +0000 (20:57 +0200)
37 files changed:
README.md [new file with mode: 0644]
pom.xml
src/main/java/net/pterodactylus/sone/core/Core.java
src/main/java/net/pterodactylus/sone/core/FreenetInterface.java
src/main/java/net/pterodactylus/sone/core/ImageInserter.java
src/main/java/net/pterodactylus/sone/core/SoneDownloaderImpl.java
src/main/java/net/pterodactylus/sone/core/SoneInserter.java
src/main/java/net/pterodactylus/sone/core/SoneModificationDetector.java
src/main/java/net/pterodactylus/sone/core/SoneParser.java
src/main/java/net/pterodactylus/sone/core/SoneUri.java
src/main/java/net/pterodactylus/sone/core/UpdateChecker.java
src/main/java/net/pterodactylus/sone/core/WebOfTrustUpdaterImpl.java
src/main/java/net/pterodactylus/sone/data/Client.java
src/main/java/net/pterodactylus/sone/data/Sone.java
src/main/java/net/pterodactylus/sone/data/impl/AlbumImpl.java
src/main/java/net/pterodactylus/sone/data/impl/IdOnlySone.java
src/main/java/net/pterodactylus/sone/data/impl/SoneImpl.java
src/main/java/net/pterodactylus/sone/database/memory/ConfigurationLoader.java
src/main/java/net/pterodactylus/sone/fcp/FcpInterface.java
src/main/java/net/pterodactylus/sone/freenet/PluginStoreConfigurationBackend.java
src/main/java/net/pterodactylus/sone/freenet/wot/IdentityChangeDetector.java
src/main/java/net/pterodactylus/sone/freenet/wot/IdentityManagerImpl.java
src/main/java/net/pterodactylus/sone/freenet/wot/Trust.java
src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustConnector.java
src/main/java/net/pterodactylus/sone/main/SonePlugin.java
src/main/java/net/pterodactylus/sone/template/SoneAccessor.java
src/main/java/net/pterodactylus/sone/text/SoneTextParser.java
src/main/java/net/pterodactylus/sone/utils/Optionals.java [deleted file]
src/main/java/net/pterodactylus/sone/web/CreateSonePage.java
src/main/java/net/pterodactylus/sone/web/LoginPage.java
src/main/java/net/pterodactylus/sone/web/SearchPage.java
src/main/java/net/pterodactylus/sone/web/UploadImagePage.java
src/main/java/net/pterodactylus/sone/web/WebInterface.java
src/main/java/net/pterodactylus/sone/web/ajax/JsonPage.java
src/main/java/net/pterodactylus/sone/web/page/FreenetTemplatePage.java
src/test/java/net/pterodactylus/sone/core/SoneModificationDetectorTest.java
src/test/java/net/pterodactylus/sone/utils/OptionalsTest.java [deleted file]

diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..c8e8a00
--- /dev/null
+++ b/README.md
@@ -0,0 +1,53 @@
+# Sone – The Social Network Plugin for Freenet
+
+Sone aims to provide social network functionality for [Freenet](https://freenetproject.org/) (also here [on GitHub](https://github.com/freenet/)).
+
+## Installing
+
+### Prerequisites
+
+For Sone to work you will need a running Freenet node, of course. You will also need the Web of Trust plugin from the official plugins listed on your node’s plugin manager page (*Configuration → Plugins* in the menu).
+
+If you already have a web of trust identity, you can skip the next section.
+
+You will also need to create an identity in the web of trust. Select *Community* from the menu, choose “Generate” and follow the instructions on-screen until your identity has been created.
+
+### Loading/Installing
+
+For Sone to work you will need a running Freenet node and the WebOfTrust plugin. Loading Sone happens from Freenet’s web interface using the “Add an Unofficial Plugin from Freenet” section from the node’s plugin manager at *Configuration → Plugins*, at the bottom of the page. Enter the key of the plugin (starting with “USK@”) into the text field and press the “Load” button. The plugin should then be downloaded from Freenet and started once it’s ready.
+
+The node will remember which plugins you loaded so that you don’t need to do that again after your node restarts (e.g. for updates).
+
+## Basic Usage
+
+### Creating a Sone
+
+Now when you select *Sone* from the menu you will be faced with the information that you do not have any Sones yet. Sone does offer you to create a Sone for your web of trust identity, though. Pick the identity you want to use in Sone and hit “Create Sone.”
+
+### Configuring Sone
+
+Choose *Sone → Options* from the menu and see through the list of options. You might want to to activate the “automatically follow new Sones” options so that your post feed does not look so empty. You can also choose to be notified for new Sones, posts, and replies.
+
+In Sone you have the ability to upload a custom avatar for your identity. However, you can not force other people to see it; see the “Avatar Options” section for how the avatar display is controlled.
+
+### Writing Posts
+
+If you choose *Sone* from the menu or simply click the large avatar on the top of the page you will be taken to the “Post Feed” page. It lists your posts as well as posts of all Sones that you follow. You can also write your own posts from here: just click inside the text field at the top of the page, it will expand into a larger box that you can write your post in. (It is also resizable and does not place a limit on the amount of text you want to write.) When you are done, hit the “Post” button right next to it. Your post will then appear at the top of your post feed.
+
+### Replying to Posts
+
+Now, a social network wouldn’t be much fun if you couldn’t talk with other people, and the easiest way to get in touch with them is to reply to one of their posts. To do so, just press the “Comment” link below a post. A text field will appear in which you can simply enter your text. You can press the “+” button on the left side to choose a different identity to post with (in case you have more than one), and once you’re done you hit the “Post Reply” button. The reply will then appear below the post you replied to.
+
+### Liking Posts and Replies
+
+“Liking” is quite the mysterious function. It doesn’t necessarily mean that you literally like something someone said, it is more of a general approval, or a “me too.” That being said, pressing this button has no other consequences than showing other people that you did press this button.
+
+## Advanced Topics
+
+### Addings Links to Posts/Replies
+
+When displaying posts and replies, Sone first parses the text. Special elements, such as Freenet URIs and Sone elements with a special syntax, are replaced with formatting that allow your browser to navigate the elements. Sone recognizes the following elements:
+
+* Links to Freenet URIs are linked to as-is. Make sure to separate the URI from surrounding text by whitespace, such as space or line breaks.
+* Links to other Sone’s profiles are added by the prefix “sone://” followed by the ID of the Sone. It is also possible to get the link for a Sone from a post or reply by that Sone; just copy the URL behind the “[link author]” link.
+* Links to other posts are added by the prefix “post://” followed by the ID of the post. You can also find the post ID behind the “[link post]” link below a post.
diff --git a/pom.xml b/pom.xml
index 4609326..a76b3b2 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -2,7 +2,7 @@
        <modelVersion>4.0.0</modelVersion>
        <groupId>net.pterodactylus</groupId>
        <artifactId>sone</artifactId>
-       <version>0.9.2</version>
+       <version>0.9.3</version>
        <dependencies>
                <dependency>
                        <groupId>net.pterodactylus</groupId>
index 73bb95f..da209d8 100644 (file)
@@ -116,7 +116,7 @@ import com.google.inject.Singleton;
 public class Core extends AbstractService implements SoneProvider, PostProvider, PostReplyProvider {
 
        /** The logger. */
-       private static final Logger logger = getLogger("Sone.Core");
+       private static final Logger logger = getLogger(Core.class.getName());
 
        /** The start time. */
        private final long startupTime = System.currentTimeMillis();
@@ -651,6 +651,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        soneInserters.put(sone, soneInserter);
                }
                loadSone(sone);
+               database.storeSone(sone);
                sone.setStatus(SoneStatus.idle);
                soneInserter.start();
                return sone;
@@ -687,8 +688,8 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        logger.log(Level.WARNING, "Given Identity is null!");
                        return null;
                }
-               final Long latestEdition = tryParse(fromNullable(
-                               identity.getProperty("Sone.LatestEdition")).or("0"));
+               String property = fromNullable(identity.getProperty("Sone.LatestEdition")).or("0");
+               long latestEdition = fromNullable(tryParse(property)).or(0L);
                Optional<Sone> existingSone = getSone(identity.getId());
                if (existingSone.isPresent() && existingSone.get().isLocal()) {
                        return existingSone.get();
@@ -1064,11 +1065,11 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                }
 
                /* load options. */
-               sone.getOptions().setAutoFollow(configuration.getBooleanValue(sonePrefix + "/Options/AutoFollow").getValue(null));
-               sone.getOptions().setSoneInsertNotificationEnabled(configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").getValue(null));
-               sone.getOptions().setShowNewSoneNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewSones").getValue(null));
-               sone.getOptions().setShowNewPostNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").getValue(null));
-               sone.getOptions().setShowNewReplyNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").getValue(null));
+               sone.getOptions().setAutoFollow(configuration.getBooleanValue(sonePrefix + "/Options/AutoFollow").getValue(false));
+               sone.getOptions().setSoneInsertNotificationEnabled(configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").getValue(false));
+               sone.getOptions().setShowNewSoneNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewSones").getValue(true));
+               sone.getOptions().setShowNewPostNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").getValue(true));
+               sone.getOptions().setShowNewReplyNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").getValue(true));
                sone.getOptions().setShowCustomAvatars(ShowCustomAvatars.valueOf(configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars").getValue(ShowCustomAvatars.NEVER.name())));
 
                /* if we’re still here, Sone was loaded successfully. */
@@ -1085,7 +1086,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        for (Album album : topLevelAlbums) {
                                sone.getRootAlbum().addAlbum(album);
                        }
-                       database.storeSone(sone);
                        synchronized (soneInserters) {
                                soneInserters.get(sone).setLastInsertFingerprint(lastInsertFingerprint);
                        }
index e802ba2..81ed069 100644 (file)
@@ -76,7 +76,7 @@ import freenet.support.io.ResumeFailedException;
 public class FreenetInterface {
 
        /** The logger. */
-       private static final Logger logger = getLogger("Sone.FreenetInterface");
+       private static final Logger logger = getLogger(FreenetInterface.class.getName());
 
        /** The event bus. */
        private final EventBus eventBus;
index 25362a6..6b33897 100644 (file)
@@ -44,7 +44,7 @@ import com.google.common.base.Function;
 public class ImageInserter {
 
        /** The logger. */
-       private static final Logger logger = getLogger("Sone.Image.Inserter");
+       private static final Logger logger = getLogger(ImageInserter.class.getName());
 
        /** The freenet interface. */
        private final FreenetInterface freenetInterface;
index b36c2d3..df5d0c1 100644 (file)
@@ -54,7 +54,7 @@ import com.google.common.annotations.VisibleForTesting;
 public class SoneDownloaderImpl extends AbstractService implements SoneDownloader {
 
        /** The logger. */
-       private static final Logger logger = getLogger("Sone.Downloader");
+       private static final Logger logger = getLogger(SoneDownloaderImpl.class.getName());
 
        /** The maximum protocol version. */
        private static final int MAX_PROTOCOL_VERSION = 0;
index 86bf049..dbb7ef1 100644 (file)
@@ -79,7 +79,7 @@ import freenet.support.io.ArrayBucket;
 public class SoneInserter extends AbstractService {
 
        /** The logger. */
-       private static final Logger logger = getLogger("Sone.Inserter");
+       private static final Logger logger = getLogger(SoneInserter.class.getName());
 
        /** The insertion delay (in seconds). */
        private static final AtomicInteger insertionDelay = new AtomicInteger(60);
@@ -180,7 +180,7 @@ public class SoneInserter extends AbstractService {
         * @return The fingerprint of the last insert
         */
        public String getLastInsertFingerprint() {
-               return soneModificationDetector.getOriginalFingerprint();
+               return soneModificationDetector.getLastInsertFingerprint();
        }
 
        /**
index 5c1c021..810241a 100644 (file)
@@ -27,8 +27,8 @@ class SoneModificationDetector {
        private final LockableFingerprintProvider lockableFingerprintProvider;
        private final AtomicInteger insertionDelay;
        private Optional<Long> lastModificationTime;
-       private String originalFingerprint;
-       private String lastFingerprint;
+       private String lastInsertFingerprint;
+       private String lastCheckFingerprint;
 
        SoneModificationDetector(LockableFingerprintProvider lockableFingerprintProvider, AtomicInteger insertionDelay) {
                this(systemTicker(), lockableFingerprintProvider, insertionDelay);
@@ -39,36 +39,36 @@ class SoneModificationDetector {
                this.ticker = ticker;
                this.lockableFingerprintProvider = lockableFingerprintProvider;
                this.insertionDelay = insertionDelay;
-               lastFingerprint = originalFingerprint;
+               lastCheckFingerprint = lastInsertFingerprint;
        }
 
        public boolean isEligibleForInsert() {
                if (lockableFingerprintProvider.isLocked()) {
                        lastModificationTime = absent();
-                       lastFingerprint = "";
+                       lastCheckFingerprint = "";
                        return false;
                }
                String fingerprint = lockableFingerprintProvider.getFingerprint();
-               if (originalFingerprint.equals(fingerprint)) {
+               if (fingerprint.equals(lastInsertFingerprint)) {
                        lastModificationTime = absent();
-                       lastFingerprint = fingerprint;
+                       lastCheckFingerprint = fingerprint;
                        return false;
                }
-               if (!lastFingerprint.equals(fingerprint)) {
+               if (!Objects.equal(lastCheckFingerprint, fingerprint)) {
                        lastModificationTime = of(ticker.read());
-                       lastFingerprint = fingerprint;
+                       lastCheckFingerprint = fingerprint;
                        return false;
                }
                return insertionDelayHasPassed();
        }
 
-       public String getOriginalFingerprint() {
-               return originalFingerprint;
+       public String getLastInsertFingerprint() {
+               return lastInsertFingerprint;
        }
 
        public void setFingerprint(String fingerprint) {
-               originalFingerprint = fingerprint;
-               lastFingerprint = originalFingerprint;
+               lastInsertFingerprint = fingerprint;
+               lastCheckFingerprint = lastInsertFingerprint;
                lastModificationTime = absent();
        }
 
@@ -77,7 +77,7 @@ class SoneModificationDetector {
        }
 
        public boolean isModified() {
-               return !Objects.equal(lockableFingerprintProvider.getFingerprint(), originalFingerprint);
+               return !Objects.equal(lockableFingerprintProvider.getFingerprint(), lastInsertFingerprint);
        }
 
        /**
index ae600a6..2712bd3 100644 (file)
@@ -38,7 +38,7 @@ import org.w3c.dom.Document;
  */
 public class SoneParser {
 
-       private static final Logger logger = getLogger("Sone.Parser");
+       private static final Logger logger = getLogger(SoneParser.class.getName());
        private static final int MAX_PROTOCOL_VERSION = 0;
        private final Core core;
 
index ea84b39..eeab32b 100644 (file)
@@ -34,7 +34,7 @@ import freenet.keys.FreenetURI;
 public class SoneUri {
 
        /** The logger. */
-       private static final Logger logger = getLogger("Sone.Data");
+       private static final Logger logger = getLogger(SoneUri.class.getName());
 
        /**
         * Generate a Sone URI from the given URI.
index 8114118..e24032f 100644 (file)
@@ -48,7 +48,7 @@ import freenet.support.api.Bucket;
 public class UpdateChecker {
 
        /** The logger. */
-       private static final Logger logger = getLogger("Sone.UpdateChecker");
+       private static final Logger logger = getLogger(UpdateChecker.class.getName());
 
        /** The event bus. */
        private final EventBus eventBus;
index 56bd8c9..9aa6dec 100644 (file)
@@ -47,7 +47,7 @@ import com.google.inject.Singleton;
 public class WebOfTrustUpdaterImpl extends AbstractService implements WebOfTrustUpdater {
 
        /** The logger. */
-       private static final Logger logger = getLogger("Sone.WoT.Updater");
+       private static final Logger logger = getLogger(WebOfTrustUpdaterImpl.class.getName());
 
        /** Stop job. */
        @SuppressWarnings("synthetic-access")
index ad094f2..612a258 100644 (file)
@@ -19,6 +19,8 @@ package net.pterodactylus.sone.data;
 
 import static com.google.common.base.Objects.equal;
 
+import com.google.common.base.Objects;
+
 /**
  * Container for the client information of a {@link Sone}.
  *
@@ -76,4 +78,9 @@ public class Client {
                return equal(getName(), client.getName()) && equal(getVersion(), client.getVersion());
        }
 
+       @Override
+       public int hashCode() {
+               return Objects.hashCode(name, version);
+       }
+
 }
index 04c5f39..e64a388 100644 (file)
@@ -171,17 +171,6 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
                }
        };
 
-       public static final Function<Sone, String> toSoneXmlUri =
-                       new Function<Sone, String>() {
-                               @Nonnull
-                               @Override
-                               public String apply(@Nullable Sone input) {
-                                       return input.getRequestUri()
-                                                       .setMetaString(new String[] { "sone.xml" })
-                                                       .toString();
-                               }
-                       };
-
        public static final Function<Sone, List<Album>> toAllAlbums = new Function<Sone, List<Album>>() {
                @Override
                public List<Album> apply(@Nullable Sone sone) {
index c569e0a..3488577 100644 (file)
@@ -134,7 +134,7 @@ public class AlbumImpl implements Album {
                checkArgument(equals(album.getParent()), "album must belong to this album");
                int oldIndex = albums.indexOf(album);
                if (oldIndex <= 0) {
-                       return null;
+                       return album;
                }
                albums.remove(oldIndex);
                albums.add(oldIndex - 1, album);
@@ -148,7 +148,7 @@ public class AlbumImpl implements Album {
                checkArgument(equals(album.getParent()), "album must belong to this album");
                int oldIndex = albums.indexOf(album);
                if ((oldIndex < 0) || (oldIndex >= (albums.size() - 1))) {
-                       return null;
+                       return album;
                }
                albums.remove(oldIndex);
                albums.add(oldIndex + 1, album);
@@ -209,7 +209,7 @@ public class AlbumImpl implements Album {
                checkArgument(image.getAlbum().equals(this), "image must belong to this album");
                int oldIndex = imageIds.indexOf(image.getId());
                if (oldIndex <= 0) {
-                       return null;
+                       return image;
                }
                imageIds.remove(image.getId());
                imageIds.add(oldIndex - 1, image.getId());
@@ -224,7 +224,7 @@ public class AlbumImpl implements Album {
                checkArgument(image.getAlbum().equals(this), "image must belong to this album");
                int oldIndex = imageIds.indexOf(image.getId());
                if ((oldIndex == -1) || (oldIndex >= (imageIds.size() - 1))) {
-                       return null;
+                       return image;
                }
                imageIds.remove(image.getId());
                imageIds.add(oldIndex + 1, image.getId());
index 0ef220b..e9a0c57 100644 (file)
@@ -18,6 +18,8 @@ import net.pterodactylus.sone.freenet.wot.Identity;
 
 import freenet.keys.FreenetURI;
 
+import com.google.common.base.Objects;
+
 /**
  * {@link Sone} implementation that only stores the ID of a Sone and returns
  * {@code null}, {@code 0}, or empty collections where appropriate.
@@ -240,4 +242,14 @@ public class IdOnlySone implements Sone {
                return id;
        }
 
+       @Override
+       public int hashCode() {
+               return id.hashCode();
+       }
+
+       @Override
+       public boolean equals(Object object) {
+               return (object != null) && (object.getClass() == getClass()) && Objects.equal(id, ((IdOnlySone) object).id);
+       }
+
 }
index 2fdd40c..f7ebfb1 100644 (file)
@@ -59,7 +59,7 @@ import com.google.common.hash.Hashing;
 public class SoneImpl implements Sone {
 
        /** The logger. */
-       private static final Logger logger = getLogger("Sone.Data");
+       private static final Logger logger = getLogger(SoneImpl.class.getName());
 
        /** The database. */
        private final Database database;
index 1691ddb..0f55575 100644 (file)
@@ -17,8 +17,7 @@ import net.pterodactylus.util.config.ConfigurationException;
  */
 public class ConfigurationLoader {
 
-       private static final Logger logger =
-                       Logger.getLogger("Sone.Database.Memory.Configuration");
+       private static final Logger logger = Logger.getLogger(ConfigurationLoader.class.getName());
        private final Configuration configuration;
 
        public ConfigurationLoader(Configuration configuration) {
index 839930c..5612a4f 100644 (file)
@@ -75,7 +75,7 @@ public class FcpInterface {
        }
 
        /** The logger. */
-       private static final Logger logger = getLogger("Sone.External.Fcp");
+       private static final Logger logger = getLogger(FcpInterface.class.getName());
 
        /** Whether the FCP interface is currently active. */
        private final AtomicBoolean active = new AtomicBoolean();
index eb16a09..eec8aa3 100644 (file)
@@ -38,7 +38,7 @@ public class PluginStoreConfigurationBackend implements ExtendedConfigurationBac
 
        /** The logger. */
        @SuppressWarnings("unused")
-       private static final Logger logger = getLogger("Sone.Fred");
+       private static final Logger logger = getLogger(PluginStoreConfigurationBackend.class.getName());
 
        /** The plugin respirator. */
        private final PluginRespirator pluginRespirator;
index a8cb62b..2fe10df 100644 (file)
@@ -142,7 +142,7 @@ public class IdentityChangeDetector {
                return new Predicate<Identity>() {
                        @Override
                        public boolean apply(Identity identity) {
-                               return identities.containsKey(identity.getId());
+                               return (identity != null) && identities.containsKey(identity.getId());
                        }
                };
        }
index 4c94749..70071f1 100644 (file)
@@ -50,7 +50,7 @@ import com.google.inject.Singleton;
 public class IdentityManagerImpl extends AbstractService implements IdentityManager {
 
        /** The logger. */
-       private static final Logger logger = getLogger("Sone.Identities");
+       private static final Logger logger = getLogger(IdentityManagerImpl.class.getName());
 
        /** The event bus. */
        private final EventBus eventBus;
index fe76846..9a70a14 100644 (file)
@@ -19,6 +19,8 @@ package net.pterodactylus.sone.freenet.wot;
 
 import static com.google.common.base.Objects.equal;
 
+import com.google.common.base.Objects;
+
 /**
  * Container class for trust in the web of trust.
  *
@@ -90,6 +92,11 @@ public class Trust {
                return equal(getExplicit(), trust.getExplicit()) && equal(getImplicit(), trust.getImplicit()) && equal(getDistance(), trust.getDistance());
        }
 
+       @Override
+       public int hashCode() {
+               return Objects.hashCode(explicit, implicit, distance);
+       }
+
        /** {@inheritDoc} */
        @Override
        public String toString() {
index 3b30a4e..6f5ef85 100644 (file)
@@ -50,7 +50,7 @@ import freenet.support.api.Bucket;
 public class WebOfTrustConnector {
 
        /** The logger. */
-       private static final Logger logger = getLogger("Sone.WoT.Connector");
+       private static final Logger logger = getLogger(WebOfTrustConnector.class.getName());
 
        /** The name of the WoT plugin. */
        private static final String WOT_PLUGIN_NAME = "plugins.WebOfTrust.WebOfTrust";
@@ -126,7 +126,7 @@ public class WebOfTrustConnector {
         *             if an error occured talking to the Web of Trust plugin
         */
        public Set<Identity> loadTrustedIdentities(OwnIdentity ownIdentity) throws PluginException {
-               return loadTrustedIdentities(ownIdentity, null);
+               return loadTrustedIdentities(ownIdentity, Optional.<String>absent());
        }
 
        /**
index b80aa47..1df4a33 100644 (file)
@@ -27,21 +27,9 @@ import java.util.logging.LogRecord;
 import java.util.logging.Logger;
 
 import net.pterodactylus.sone.core.Core;
-import net.pterodactylus.sone.core.FreenetInterface;
-import net.pterodactylus.sone.core.WebOfTrustUpdater;
-import net.pterodactylus.sone.core.WebOfTrustUpdaterImpl;
-import net.pterodactylus.sone.database.Database;
-import net.pterodactylus.sone.database.PostBuilderFactory;
-import net.pterodactylus.sone.database.PostProvider;
-import net.pterodactylus.sone.database.PostReplyBuilderFactory;
-import net.pterodactylus.sone.database.SoneProvider;
-import net.pterodactylus.sone.database.memory.MemoryDatabase;
 import net.pterodactylus.sone.fcp.FcpInterface;
 import net.pterodactylus.sone.freenet.PluginStoreConfigurationBackend;
-import net.pterodactylus.sone.freenet.plugin.PluginConnector;
 import net.pterodactylus.sone.freenet.wot.Context;
-import net.pterodactylus.sone.freenet.wot.IdentityManager;
-import net.pterodactylus.sone.freenet.wot.IdentityManagerImpl;
 import net.pterodactylus.sone.freenet.wot.WebOfTrustConnector;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.util.config.Configuration;
@@ -50,11 +38,13 @@ import net.pterodactylus.util.config.MapConfigurationBackend;
 import net.pterodactylus.util.version.Version;
 
 import com.google.common.base.Optional;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
 import com.google.common.eventbus.EventBus;
 import com.google.inject.AbstractModule;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
-import com.google.inject.Singleton;
 import com.google.inject.TypeLiteral;
 import com.google.inject.matcher.Matchers;
 import com.google.inject.spi.InjectionListener;
@@ -84,24 +74,34 @@ import freenet.support.api.Bucket;
  */
 public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, FredPluginBaseL10n, FredPluginThreadless, FredPluginVersioned {
 
+       private static final Logger soneLogger = getLogger("net.pterodactylus.sone");
+
        static {
                /* initialize logging. */
-               Logger soneLogger = getLogger("Sone");
                soneLogger.setUseParentHandlers(false);
                soneLogger.addHandler(new Handler() {
+                       private final LoadingCache<String, Class<?>> classCache = CacheBuilder.newBuilder()
+                                       .build(new CacheLoader<String, Class<?>>() {
+                                               @Override
+                                               public Class<?> load(String key) throws Exception {
+                                                       return Class.forName(key);
+                                               }
+                                       });
+
                        @Override
                        public void publish(LogRecord logRecord) {
                                int recordLevel = logRecord.getLevel().intValue();
+                               Class<?> loggingClass = classCache.getUnchecked(logRecord.getLoggerName());
                                if (recordLevel < Level.FINE.intValue()) {
-                                       freenet.support.Logger.debug(logRecord.getLoggerName(), logRecord.getMessage(), logRecord.getThrown());
+                                       freenet.support.Logger.debug(loggingClass, logRecord.getMessage(), logRecord.getThrown());
                                } else if (recordLevel < Level.INFO.intValue()) {
-                                       freenet.support.Logger.minor(logRecord.getLoggerName(), logRecord.getMessage(), logRecord.getThrown());
+                                       freenet.support.Logger.minor(loggingClass, logRecord.getMessage(), logRecord.getThrown());
                                } else if (recordLevel < Level.WARNING.intValue()) {
-                                       freenet.support.Logger.normal(logRecord.getLoggerName(), logRecord.getMessage(), logRecord.getThrown());
+                                       freenet.support.Logger.normal(loggingClass, logRecord.getMessage(), logRecord.getThrown());
                                } else if (recordLevel < Level.SEVERE.intValue()) {
-                                       freenet.support.Logger.warning(logRecord.getLoggerName(), logRecord.getMessage(), logRecord.getThrown());
+                                       freenet.support.Logger.warning(loggingClass, logRecord.getMessage(), logRecord.getThrown());
                                } else {
-                                       freenet.support.Logger.error(logRecord.getLoggerName(), logRecord.getMessage(), logRecord.getThrown());
+                                       freenet.support.Logger.error(loggingClass, logRecord.getMessage(), logRecord.getThrown());
                                }
                        }
 
@@ -116,15 +116,15 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
        }
 
        /** The version. */
-       public static final Version VERSION = new Version(0, 9, 2);
+       public static final Version VERSION = new Version(0, 9, 3);
 
        /** The current year at time of release. */
        private static final int YEAR = 2015;
        private static final String SONE_HOMEPAGE = "USK@nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI,DuQSUZiI~agF8c-6tjsFFGuZ8eICrzWCILB60nT8KKo,AQACAAE/sone/";
-       private static final int LATEST_EDITION = 69;
+       private static final int LATEST_EDITION = 70;
 
        /** The logger. */
-       private static final Logger logger = getLogger("Sone.Plugin");
+       private static final Logger logger = getLogger(SonePlugin.class.getName());
 
        /** The plugin respirator. */
        private PluginRespirator pluginRespirator;
@@ -295,6 +295,7 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
         */
        @Override
        public void terminate() {
+               deregisterLoggerHandlers();
                try {
                        /* stop the web interface. */
                        webInterface.stop();
@@ -309,6 +310,12 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
                }
        }
 
+       private void deregisterLoggerHandlers() {
+               for (Handler handler : soneLogger.getHandlers()) {
+                       soneLogger.removeHandler(handler);
+               }
+       }
+
        //
        // INTERFACE FredPluginFCP
        //
index 711332a..03973a1 100644 (file)
@@ -58,7 +58,7 @@ import net.pterodactylus.util.template.TemplateContext;
 public class SoneAccessor extends ReflectionAccessor {
 
        /** The logger. */
-       private static final Logger logger = getLogger("Sone.Data");
+       private static final Logger logger = getLogger(SoneAccessor.class.getName());
 
        /** The core. */
        private final Core core;
index cc5c59b..a1675d7 100644 (file)
@@ -47,7 +47,7 @@ import freenet.keys.FreenetURI;
 public class SoneTextParser implements Parser<SoneTextParserContext> {
 
        /** The logger. */
-       private static final Logger logger = getLogger("Sone.Data.Parser");
+       private static final Logger logger = getLogger(SoneTextParser.class.getName());
 
        /** Pattern to detect whitespace. */
        private static final Pattern whitespacePattern = Pattern.compile("[\\u000a\u0020\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u200c\u200d\u202f\u205f\u2060\u2800\u3000]");
@@ -59,41 +59,21 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
         */
        private enum LinkType {
 
-               /** Link is a KSK. */
-               KSK("KSK@"),
+               KSK("KSK@", true),
+               CHK("CHK@", true),
+               SSK("SSK@", true),
+               USK("USK@", true),
+               HTTP("http://", false),
+               HTTPS("https://", false),
+               SONE("sone://", false),
+               POST("post://", false);
 
-               /** Link is a CHK. */
-               CHK("CHK@"),
-
-               /** Link is an SSK. */
-               SSK("SSK@"),
-
-               /** Link is a USK. */
-               USK("USK@"),
-
-               /** Link is HTTP. */
-               HTTP("http://"),
-
-               /** Link is HTTPS. */
-               HTTPS("https://"),
-
-               /** Link is a Sone. */
-               SONE("sone://"),
-
-               /** Link is a post. */
-               POST("post://");
-
-               /** The scheme identifying this link type. */
                private final String scheme;
+               private final boolean freenetLink;
 
-               /**
-                * Creates a new link type identified by the given scheme.
-                *
-                * @param scheme
-                *            The scheme of the link type
-                */
-               private LinkType(String scheme) {
+               LinkType(String scheme, boolean freenetLink) {
                        this.scheme = scheme;
+                       this.freenetLink = freenetLink;
                }
 
                /**
@@ -105,6 +85,10 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                        return scheme;
                }
 
+               public boolean isFreenetLink() {
+                       return freenetLink;
+               }
+
        }
 
        /** The Sone provider. */
@@ -159,15 +143,8 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                                 */
                                boolean lineComplete = true;
                                while (line.length() > 0) {
-                                       int nextKsk = line.indexOf("KSK@");
-                                       int nextChk = line.indexOf("CHK@");
-                                       int nextSsk = line.indexOf("SSK@");
-                                       int nextUsk = line.indexOf("USK@");
-                                       int nextHttp = line.indexOf("http://");
-                                       int nextHttps = line.indexOf("https://");
-                                       int nextSone = line.indexOf("sone://");
-                                       int nextPost = line.indexOf("post://");
-                                       if ((nextKsk == -1) && (nextChk == -1) && (nextSsk == -1) && (nextUsk == -1) && (nextHttp == -1) && (nextHttps == -1) && (nextSone == -1) && (nextPost == -1)) {
+                                       Optional<NextLink> nextLink = NextLink.findNextLink(line);
+                                       if (!nextLink.isPresent()) {
                                                if (lineComplete && !lastLineEmpty) {
                                                        parts.add(new PlainTextPart("\n" + line));
                                                } else {
@@ -175,43 +152,11 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                                                }
                                                break;
                                        }
-                                       int next = Integer.MAX_VALUE;
-                                       LinkType linkType = null;
-                                       if ((nextKsk > -1) && (nextKsk < next)) {
-                                               next = nextKsk;
-                                               linkType = LinkType.KSK;
-                                       }
-                                       if ((nextChk > -1) && (nextChk < next)) {
-                                               next = nextChk;
-                                               linkType = LinkType.CHK;
-                                       }
-                                       if ((nextSsk > -1) && (nextSsk < next)) {
-                                               next = nextSsk;
-                                               linkType = LinkType.SSK;
-                                       }
-                                       if ((nextUsk > -1) && (nextUsk < next)) {
-                                               next = nextUsk;
-                                               linkType = LinkType.USK;
-                                       }
-                                       if ((nextHttp > -1) && (nextHttp < next)) {
-                                               next = nextHttp;
-                                               linkType = LinkType.HTTP;
-                                       }
-                                       if ((nextHttps > -1) && (nextHttps < next)) {
-                                               next = nextHttps;
-                                               linkType = LinkType.HTTPS;
-                                       }
-                                       if ((nextSone > -1) && (nextSone < next)) {
-                                               next = nextSone;
-                                               linkType = LinkType.SONE;
-                                       }
-                                       if ((nextPost > -1) && (nextPost < next)) {
-                                               next = nextPost;
-                                               linkType = LinkType.POST;
-                                       }
+                                       LinkType linkType = nextLink.get().getLinkType();
+                                       int next = nextLink.get().getPosition();
 
                                        /* cut off “freenet:” from before keys. */
-                                       if (((linkType == LinkType.KSK) || (linkType == LinkType.CHK) || (linkType == LinkType.SSK) || (linkType == LinkType.USK)) && (next >= 8) && (line.substring(next - 8, next).equals("freenet:"))) {
+                                       if (linkType.isFreenetLink() && (next >= 8) && (line.substring(next - 8, next).equals("freenet:"))) {
                                                next -= 8;
                                                line = line.substring(0, next) + line.substring(next + 8);
                                        }
@@ -223,7 +168,6 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                                        if (next > 0) {
                                                parts.add(new PlainTextPart(line.substring(0, next)));
                                                line = line.substring(next);
-                                               next = 0;
                                        }
                                        lineComplete = false;
 
@@ -232,7 +176,6 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                                        String link = line.substring(0, nextSpace);
                                        String name = link;
                                        logger.log(Level.FINER, String.format("Found link: %s", link));
-                                       logger.log(Level.FINEST, String.format("CHK: %d, SSK: %d, USK: %d", nextChk, nextSsk, nextUsk));
 
                                        /* if there is no text after the scheme, it’s not a link! */
                                        if (link.equals(linkType.getScheme())) {
@@ -277,7 +220,7 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                                                continue;
                                        }
 
-                                       if ((linkType == LinkType.KSK) || (linkType == LinkType.CHK) || (linkType == LinkType.SSK) || (linkType == LinkType.USK)) {
+                                       if (linkType.isFreenetLink()) {
                                                FreenetURI uri;
                                                if (name.indexOf('?') > -1) {
                                                        name = name.substring(0, name.indexOf('?'));
@@ -335,7 +278,7 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                }
                for (int partIndex = parts.size() - 1; partIndex >= 0; --partIndex) {
                        Part part = parts.getPart(partIndex);
-                       if (!(part instanceof PlainTextPart) || !"\n".equals(((PlainTextPart) part).getText())) {
+                       if (!(part instanceof PlainTextPart) || !"\n".equals(part.getText())) {
                                break;
                        }
                        parts.removePart(partIndex);
@@ -343,4 +286,40 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                return parts;
        }
 
+       private static class NextLink {
+
+               private final int position;
+               private final LinkType linkType;
+
+               private NextLink(int position, LinkType linkType) {
+                       this.position = position;
+                       this.linkType = linkType;
+               }
+
+               public int getPosition() {
+                       return position;
+               }
+
+               public LinkType getLinkType() {
+                       return linkType;
+               }
+
+               public static Optional<NextLink> findNextLink(String line) {
+                       int earliestLinkPosition = Integer.MAX_VALUE;
+                       LinkType linkType = null;
+                       for (LinkType possibleLinkType : LinkType.values()) {
+                               int nextLinkPosition = line.indexOf(possibleLinkType.getScheme());
+                               if (nextLinkPosition > -1) {
+                                       if (nextLinkPosition < earliestLinkPosition) {
+                                               earliestLinkPosition = nextLinkPosition;
+                                               linkType = possibleLinkType;
+                                       }
+                               }
+                       }
+                       return earliestLinkPosition < Integer.MAX_VALUE ?
+                                       Optional.of(new NextLink(earliestLinkPosition, linkType)) : Optional.<NextLink>absent();
+               }
+
+       }
+
 }
diff --git a/src/main/java/net/pterodactylus/sone/utils/Optionals.java b/src/main/java/net/pterodactylus/sone/utils/Optionals.java
deleted file mode 100644 (file)
index 67132aa..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-package net.pterodactylus.sone.utils;
-
-import com.google.common.base.Function;
-import com.google.common.base.Optional;
-import com.google.common.base.Predicate;
-
-/**
- * Helper methods for dealing with {@link Optional}s.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class Optionals {
-
-       public static Predicate<Optional<?>> isPresent() {
-               return new Predicate<Optional<?>>() {
-                       @Override
-                       public boolean apply(Optional<?> input) {
-                               return input.isPresent();
-                       }
-               };
-       }
-
-       public static <T> Function<Optional<T>, T> get() {
-               return new Function<Optional<T>, T>() {
-                       @Override
-                       public T apply(Optional<T> input) {
-                               return input.get();
-                       }
-               };
-       }
-
-}
index b2d4578..ef2d619 100644 (file)
@@ -44,7 +44,7 @@ import freenet.clients.http.ToadletContext;
 public class CreateSonePage extends SoneTemplatePage {
 
        /** The logger. */
-       private static final Logger logger = getLogger("Sone.Web.CreateSone");
+       private static final Logger logger = getLogger(CreateSonePage.class.getName());
 
        /**
         * Creates a new “create Sone” page.
index 58e33a1..b5c608c 100644 (file)
@@ -41,7 +41,7 @@ public class LoginPage extends SoneTemplatePage {
 
        /** The logger. */
        @SuppressWarnings("unused")
-       private static final Logger logger = getLogger("Sone.Web.Login");
+       private static final Logger logger = getLogger(LoginPage.class.getName());
 
        /**
         * Creates a new login page.
index 3efba95..95b0bba 100644 (file)
@@ -64,7 +64,7 @@ import com.google.common.collect.Ordering;
 public class SearchPage extends SoneTemplatePage {
 
        /** The logger. */
-       private static final Logger logger = getLogger("Sone.Web.Search");
+       private static final Logger logger = getLogger(SearchPage.class.getName());
 
        /** Short-term cache. */
        private final LoadingCache<List<Phrase>, Set<Hit<Post>>> hitCache = CacheBuilder.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).build(new CacheLoader<List<Phrase>, Set<Hit<Post>>>() {
index 5e23a78..a1e987a 100644 (file)
@@ -17,6 +17,7 @@
 
 package net.pterodactylus.sone.web;
 
+import static com.google.common.base.Optional.fromNullable;
 import static java.util.logging.Logger.getLogger;
 
 import java.awt.Image;
@@ -55,8 +56,8 @@ import freenet.support.api.HTTPUploadedFile;
  */
 public class UploadImagePage extends SoneTemplatePage {
 
-       /** The logger. */
-       private static final Logger logger = getLogger("Sone.Web.UploadImage");
+       private static final Logger logger = getLogger(UploadImagePage.class.getName());
+       private static final String UNKNOWN_MIME_TYPE = "application/octet-stream";
 
        /**
         * Creates a new “upload image” page.
@@ -154,12 +155,13 @@ public class UploadImagePage extends SoneTemplatePage {
                        ImageInputStream imageInputStream = ImageIO.createImageInputStream(imageDataInputStream);
                        Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(imageInputStream);
                        if (imageReaders.hasNext()) {
-                               return imageReaders.next().getOriginatingProvider().getMIMETypes()[0];
+                               return fromNullable(imageReaders.next().getOriginatingProvider().getMIMETypes())
+                                               .or(new String[] { UNKNOWN_MIME_TYPE })[0];
                        }
                } catch (IOException ioe1) {
                        logger.log(Level.FINE, "Could not detect MIME type for image.", ioe1);
                }
-               return "application/octet-stream";
+               return UNKNOWN_MIME_TYPE;
        }
 
 }
index e813179..fbba8e0 100644 (file)
@@ -174,7 +174,7 @@ import freenet.support.api.HTTPRequest;
 public class WebInterface {
 
        /** The logger. */
-       private static final Logger logger = getLogger("Sone.Web.Main");
+       private static final Logger logger = getLogger(WebInterface.class.getName());
 
        /** The notification manager. */
        private final NotificationManager notificationManager = new NotificationManager();
index 6d6d92a..2392b12 100644 (file)
@@ -48,7 +48,7 @@ import freenet.clients.http.ToadletContext;
 public abstract class JsonPage implements FreenetPage {
 
        /** The logger. */
-       private static final Logger logger = getLogger("Sone.Web.Ajax");
+       private static final Logger logger = getLogger(JsonPage.class.getName());
 
        /** The JSON serializer. */
        private static final ObjectMapper objectMapper = new ObjectMapper();
index 40e19b1..5e30ca0 100644 (file)
@@ -52,7 +52,7 @@ import freenet.support.HTMLNode;
 public class FreenetTemplatePage implements FreenetPage, LinkEnabledCallback {
 
        /** The logger. */
-       private static final Logger logger = getLogger("Sone.Web.Freenet");
+       private static final Logger logger = getLogger(FreenetTemplatePage.class.getName());
 
        /** The path of the page. */
        private final String path;
index d70cc5c..a45556a 100644 (file)
@@ -9,7 +9,6 @@ import static org.mockito.Mockito.when;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import net.pterodactylus.sone.core.SoneModificationDetector.LockableFingerprintProvider;
-import net.pterodactylus.sone.data.Sone;
 
 import com.google.common.base.Ticker;
 import org.junit.Before;
@@ -71,7 +70,7 @@ public class SoneModificationDetectorTest {
 
        @Test
        public void originalFingerprintIsRetained() {
-               assertThat(soneModificationDetector.getOriginalFingerprint(), is("original"));
+               assertThat(soneModificationDetector.getLastInsertFingerprint(), is("original"));
        }
 
        @Test
@@ -152,4 +151,34 @@ public class SoneModificationDetectorTest {
                assertThat(soneModificationDetector.isEligibleForInsert(), is(false));
        }
 
+       @Test
+       public void soneWithoutOriginalFingerprintIsNotEligibleAfter59Seconds() {
+               SoneModificationDetector soneModificationDetector = createDetectorWithoutOriginalFingerprint();
+               assertThat(soneModificationDetector.isEligibleForInsert(), is(false));
+               passTime(59);
+               assertThat(soneModificationDetector.isEligibleForInsert(), is(false));
+       }
+
+       private SoneModificationDetector createDetectorWithoutOriginalFingerprint() {
+               return new SoneModificationDetector(ticker, new LockableFingerprintProvider() {
+                       @Override
+                       public boolean isLocked() {
+                               return false;
+                       }
+
+                       @Override
+                       public String getFingerprint() {
+                               return "changed";
+                       }
+               }, insertionDelay);
+       }
+
+       @Test
+       public void soneWithoutOriginalFingerprintIsEligibleAfter60Seconds() {
+               SoneModificationDetector soneModificationDetector = createDetectorWithoutOriginalFingerprint();
+               assertThat(soneModificationDetector.isEligibleForInsert(), is(false));
+               passTime(60);
+               assertThat(soneModificationDetector.isEligibleForInsert(), is(true));
+       }
+
 }
diff --git a/src/test/java/net/pterodactylus/sone/utils/OptionalsTest.java b/src/test/java/net/pterodactylus/sone/utils/OptionalsTest.java
deleted file mode 100644 (file)
index 0f7dee3..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-package net.pterodactylus.sone.utils;
-
-import java.util.Arrays;
-import java.util.List;
-
-import com.google.common.base.Optional;
-import com.google.common.collect.FluentIterable;
-import org.hamcrest.MatcherAssert;
-import org.hamcrest.Matchers;
-import org.junit.Test;
-
-/**
- * Unit test for {@link Optionals}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class OptionalsTest {
-
-       private final Object object1 = new Object();
-       private final Object object2 = new Object();
-       private final Object object3 = new Object();
-
-       @Test
-       public void canCreateOptionals() {
-               new Optionals();
-       }
-
-       @Test
-       public void isPresentFiltersCorrectOptionals() {
-               List<Optional<Object>> optionals = Arrays.asList(
-                               Optional.of(object1), Optional.absent(),
-                               Optional.of(object2), Optional.absent(),
-                               Optional.of(object3), Optional.absent()
-               );
-               List<Optional<Object>> filteredOptionals =
-                               FluentIterable.from(optionals).filter(Optionals.isPresent()).toList();
-               MatcherAssert.assertThat(filteredOptionals, Matchers.contains(
-                               Optional.of(object1), Optional.of(object2), Optional.of(object3)));
-       }
-
-       @Test
-       public void getReturnsCorrectValues() {
-               List<Optional<Object>> optionals = Arrays.asList(
-                               Optional.of(object1),
-                               Optional.of(object2),
-                               Optional.of(object3)
-               );
-               List<Object> objects = FluentIterable.from(optionals).transform(Optionals.get()).toList();
-               MatcherAssert.assertThat(objects, Matchers.contains(object1, object2, object3));
-       }
-
-}