From: David ‘Bombe’ Roden Date: Sat, 15 Jan 2011 22:14:09 +0000 (+0100) Subject: Merge branch 'release-0.4' X-Git-Tag: 0.4^0 X-Git-Url: https://git.pterodactylus.net/?p=Sone.git;a=commitdiff_plain;h=d53a79eeb9531a08173431ce2ba19156eeca4a0c;hp=fa63b855e28c5fbf270bfdca1bb23fd048affae5 Merge branch 'release-0.4' --- diff --git a/pom.xml b/pom.xml index eae705c..b315d28 100644 --- a/pom.xml +++ b/pom.xml @@ -2,12 +2,12 @@ 4.0.0 net.pterodactylus sone - 0.3.7 + 0.4 net.pterodactylus utils - 0.7.7 + 0.7.8 junit diff --git a/src/main/java/net/pterodactylus/sone/core/Core.java b/src/main/java/net/pterodactylus/sone/core/Core.java index 78df9ef..27e8db8 100644 --- a/src/main/java/net/pterodactylus/sone/core/Core.java +++ b/src/main/java/net/pterodactylus/sone/core/Core.java @@ -34,17 +34,21 @@ import net.pterodactylus.sone.core.Options.OptionWatcher; import net.pterodactylus.sone.data.Client; import net.pterodactylus.sone.data.Post; import net.pterodactylus.sone.data.Profile; +import net.pterodactylus.sone.data.Profile.Field; import net.pterodactylus.sone.data.Reply; import net.pterodactylus.sone.data.Sone; import net.pterodactylus.sone.freenet.wot.Identity; import net.pterodactylus.sone.freenet.wot.IdentityListener; import net.pterodactylus.sone.freenet.wot.IdentityManager; import net.pterodactylus.sone.freenet.wot.OwnIdentity; +import net.pterodactylus.sone.freenet.wot.Trust; +import net.pterodactylus.sone.freenet.wot.WebOfTrustException; import net.pterodactylus.sone.main.SonePlugin; import net.pterodactylus.util.config.Configuration; import net.pterodactylus.util.config.ConfigurationException; import net.pterodactylus.util.logging.Logging; import net.pterodactylus.util.number.Numbers; +import net.pterodactylus.util.validation.Validation; import net.pterodactylus.util.version.Version; import freenet.keys.FreenetURI; @@ -151,6 +155,9 @@ public class Core implements IdentityListener, UpdateListener { /** All known replies. */ private Set knownReplies = new HashSet(); + /** Trusted identities, sorted by own identities. */ + private Map> trustedIdentities = Collections.synchronizedMap(new HashMap>()); + /** * Creates a new core. * @@ -517,6 +524,19 @@ public class Core implements IdentityListener, UpdateListener { } /** + * Returns whether the target Sone is trusted by the origin Sone. + * + * @param origin + * The origin Sone + * @param target + * The target Sone + * @return {@code true} if the target Sone is trusted by the origin Sone + */ + public boolean isSoneTrusted(Sone origin, Sone target) { + return trustedIdentities.containsKey(origin) && trustedIdentities.get(origin.getIdentity()).contains(target); + } + + /** * Returns the post with the given ID. * * @param postId @@ -839,7 +859,12 @@ public class Core implements IdentityListener, UpdateListener { * @return The created Sone */ public Sone createSone(OwnIdentity ownIdentity) { - identityManager.addContext(ownIdentity, "Sone"); + try { + ownIdentity.addContext("Sone"); + } catch (WebOfTrustException wote1) { + logger.log(Level.SEVERE, "Could not add “Sone” context to own identity: " + ownIdentity, wote1); + return null; + } Sone sone = addLocalSone(ownIdentity); return sone; } @@ -889,6 +914,97 @@ public class Core implements IdentityListener, UpdateListener { } /** + * Retrieves the trust relationship from the origin to the target. If the + * trust relationship can not be retrieved, {@code null} is returned. + * + * @see Identity#getTrust(OwnIdentity) + * @param origin + * The origin of the trust tree + * @param target + * The target of the trust + * @return The trust relationship + */ + public Trust getTrust(Sone origin, Sone target) { + if (!isLocalSone(origin)) { + logger.log(Level.WARNING, "Tried to get trust from remote Sone: %s", origin); + return null; + } + return target.getIdentity().getTrust((OwnIdentity) origin.getIdentity()); + } + + /** + * Sets the trust value of the given origin Sone for the target Sone. + * + * @param origin + * The origin Sone + * @param target + * The target Sone + * @param trustValue + * The trust value (from {@code -100} to {@code 100}) + */ + public void setTrust(Sone origin, Sone target, int trustValue) { + Validation.begin().isNotNull("Trust Origin", origin).check().isInstanceOf("Trust Origin", origin.getIdentity(), OwnIdentity.class).isNotNull("Trust Target", target).isLessOrEqual("Trust Value", trustValue, 100).isGreaterOrEqual("Trust Value", trustValue, -100).check(); + try { + ((OwnIdentity) origin.getIdentity()).setTrust(target.getIdentity(), trustValue, options.getStringOption("TrustComment").get()); + } catch (WebOfTrustException wote1) { + logger.log(Level.WARNING, "Could not set trust for Sone: " + target, wote1); + } + } + + /** + * Removes any trust assignment for the given target Sone. + * + * @param origin + * The trust origin + * @param target + * The trust target + */ + public void removeTrust(Sone origin, Sone target) { + Validation.begin().isNotNull("Trust Origin", origin).isNotNull("Trust Target", target).check().isInstanceOf("Trust Origin Identity", origin.getIdentity(), OwnIdentity.class).check(); + try { + ((OwnIdentity) origin.getIdentity()).removeTrust(target.getIdentity()); + } catch (WebOfTrustException wote1) { + logger.log(Level.WARNING, "Could not remove trust for Sone: " + target, wote1); + } + } + + /** + * Assigns the configured positive trust value for the given target. + * + * @param origin + * The trust origin + * @param target + * The trust target + */ + public void trustSone(Sone origin, Sone target) { + setTrust(origin, target, options.getIntegerOption("PositiveTrust").get()); + } + + /** + * Assigns the configured negative trust value for the given target. + * + * @param origin + * The trust origin + * @param target + * The trust target + */ + public void distrustSone(Sone origin, Sone target) { + setTrust(origin, target, options.getIntegerOption("NegativeTrust").get()); + } + + /** + * Removes the trust assignment for the given target. + * + * @param origin + * The trust origin + * @param target + * The trust target + */ + public void untrustSone(Sone origin, Sone target) { + removeTrust(origin, target); + } + + /** * Updates the stores Sone with the given Sone. * * @param sone @@ -993,8 +1109,12 @@ public class Core implements IdentityListener, UpdateListener { localSones.remove(sone.getId()); soneInserters.remove(sone).stop(); } - identityManager.removeContext((OwnIdentity) sone.getIdentity(), "Sone"); - identityManager.removeProperty((OwnIdentity) sone.getIdentity(), "Sone.LatestEdition"); + try { + ((OwnIdentity) sone.getIdentity()).removeContext("Sone"); + ((OwnIdentity) sone.getIdentity()).removeProperty("Sone.LatestEdition"); + } catch (WebOfTrustException wote1) { + logger.log(Level.WARNING, "Could not remove context and properties from Sone: " + sone, wote1); + } try { configuration.getLongValue("Sone/" + sone.getId() + "/Time").setValue(null); } catch (ConfigurationException ce1) { @@ -1033,6 +1153,17 @@ public class Core implements IdentityListener, UpdateListener { profile.setBirthMonth(configuration.getIntValue(sonePrefix + "/Profile/BirthMonth").getValue(null)); profile.setBirthYear(configuration.getIntValue(sonePrefix + "/Profile/BirthYear").getValue(null)); + /* load profile fields. */ + while (true) { + String fieldPrefix = sonePrefix + "/Profile/Fields/" + profile.getFields().size(); + String fieldName = configuration.getStringValue(fieldPrefix + "/Name").getValue(null); + if (fieldName == null) { + break; + } + String fieldValue = configuration.getStringValue(fieldPrefix + "/Value").getValue(""); + profile.addField(fieldName).setValue(fieldValue); + } + /* load posts. */ Set posts = new HashSet(); while (true) { @@ -1149,8 +1280,9 @@ public class Core implements IdentityListener, UpdateListener { } logger.log(Level.INFO, "Saving Sone: %s", sone); - identityManager.setProperty((OwnIdentity) sone.getIdentity(), "Sone.LatestEdition", String.valueOf(sone.getLatestEdition())); try { + ((OwnIdentity) sone.getIdentity()).setProperty("Sone.LatestEdition", String.valueOf(sone.getLatestEdition())); + /* save Sone into configuration. */ String sonePrefix = "Sone/" + sone.getId(); configuration.getLongValue(sonePrefix + "/Time").setValue(sone.getTime()); @@ -1165,6 +1297,15 @@ public class Core implements IdentityListener, UpdateListener { configuration.getIntValue(sonePrefix + "/Profile/BirthMonth").setValue(profile.getBirthMonth()); configuration.getIntValue(sonePrefix + "/Profile/BirthYear").setValue(profile.getBirthYear()); + /* save profile fields. */ + int fieldCounter = 0; + for (Field profileField : profile.getFields()) { + String fieldPrefix = sonePrefix + "/Profile/Fields/" + fieldCounter++; + configuration.getStringValue(fieldPrefix + "/Name").setValue(profileField.getName()); + configuration.getStringValue(fieldPrefix + "/Value").setValue(profileField.getValue()); + } + configuration.getStringValue(sonePrefix + "/Profile/Fields/" + fieldCounter + "/Name").setValue(null); + /* save posts. */ int postCounter = 0; for (Post post : sone.getPosts()) { @@ -1212,6 +1353,8 @@ public class Core implements IdentityListener, UpdateListener { logger.log(Level.INFO, "Sone %s saved.", sone); } catch (ConfigurationException ce1) { logger.log(Level.WARNING, "Could not save Sone: " + sone, ce1); + } catch (WebOfTrustException wote1) { + logger.log(Level.WARNING, "Could not set WoT property for Sone: " + sone, wote1); } } @@ -1450,6 +1593,9 @@ public class Core implements IdentityListener, UpdateListener { try { configuration.getIntValue("Option/ConfigurationVersion").setValue(0); configuration.getIntValue("Option/InsertionDelay").setValue(options.getIntegerOption("InsertionDelay").getReal()); + configuration.getIntValue("Option/PositiveTrust").setValue(options.getIntegerOption("PositiveTrust").getReal()); + configuration.getIntValue("Option/NegativeTrust").setValue(options.getIntegerOption("NegativeTrust").getReal()); + configuration.getStringValue("Option/TrustComment").setValue(options.getStringOption("TrustComment").getReal()); configuration.getBooleanValue("Option/SoneRescueMode").setValue(options.getBooleanOption("SoneRescueMode").getReal()); configuration.getBooleanValue("Option/ClearOnNextRestart").setValue(options.getBooleanOption("ClearOnNextRestart").getReal()); configuration.getBooleanValue("Option/ReallyClearOnNextRestart").setValue(options.getBooleanOption("ReallyClearOnNextRestart").getReal()); @@ -1511,6 +1657,9 @@ public class Core implements IdentityListener, UpdateListener { } })); + options.addIntegerOption("PositiveTrust", new DefaultOption(75)); + options.addIntegerOption("NegativeTrust", new DefaultOption(-100)); + options.addStringOption("TrustComment", new DefaultOption("Set from Sone Web Interface")); options.addBooleanOption("SoneRescueMode", new DefaultOption(false)); options.addBooleanOption("ClearOnNextRestart", new DefaultOption(false)); options.addBooleanOption("ReallyClearOnNextRestart", new DefaultOption(false)); @@ -1527,6 +1676,9 @@ public class Core implements IdentityListener, UpdateListener { } options.getIntegerOption("InsertionDelay").set(configuration.getIntValue("Option/InsertionDelay").getValue(null)); + options.getIntegerOption("PositiveTrust").set(configuration.getIntValue("Option/PositiveTrust").getValue(null)); + options.getIntegerOption("NegativeTrust").set(configuration.getIntValue("Option/NegativeTrust").getValue(null)); + options.getStringOption("TrustComment").set(configuration.getStringValue("Option/TrustComment").getValue(null)); options.getBooleanOption("SoneRescueMode").set(configuration.getBooleanValue("Option/SoneRescueMode").getValue(null)); /* load known Sones. */ @@ -1595,6 +1747,7 @@ public class Core implements IdentityListener, UpdateListener { public void ownIdentityAdded(OwnIdentity ownIdentity) { logger.log(Level.FINEST, "Adding OwnIdentity: " + ownIdentity); if (ownIdentity.hasContext("Sone")) { + trustedIdentities.put(ownIdentity, Collections.synchronizedSet(new HashSet())); addLocalSone(ownIdentity); } } @@ -1605,14 +1758,16 @@ public class Core implements IdentityListener, UpdateListener { @Override public void ownIdentityRemoved(OwnIdentity ownIdentity) { logger.log(Level.FINEST, "Removing OwnIdentity: " + ownIdentity); + trustedIdentities.remove(ownIdentity); } /** * {@inheritDoc} */ @Override - public void identityAdded(Identity identity) { + public void identityAdded(OwnIdentity ownIdentity, Identity identity) { logger.log(Level.FINEST, "Adding Identity: " + identity); + trustedIdentities.get(ownIdentity).add(identity); addRemoteSone(identity); } @@ -1620,13 +1775,14 @@ public class Core implements IdentityListener, UpdateListener { * {@inheritDoc} */ @Override - public void identityUpdated(final Identity identity) { + public void identityUpdated(OwnIdentity ownIdentity, final Identity identity) { new Thread(new Runnable() { @Override @SuppressWarnings("synthetic-access") public void run() { Sone sone = getRemoteSone(identity.getId()); + sone.setIdentity(identity); soneDownloader.fetchSone(sone); } }).start(); @@ -1636,8 +1792,8 @@ public class Core implements IdentityListener, UpdateListener { * {@inheritDoc} */ @Override - public void identityRemoved(Identity identity) { - /* TODO */ + public void identityRemoved(OwnIdentity ownIdentity, Identity identity) { + trustedIdentities.get(ownIdentity).remove(identity); } // diff --git a/src/main/java/net/pterodactylus/sone/core/Options.java b/src/main/java/net/pterodactylus/sone/core/Options.java index f77c085..b7ece81 100644 --- a/src/main/java/net/pterodactylus/sone/core/Options.java +++ b/src/main/java/net/pterodactylus/sone/core/Options.java @@ -161,6 +161,9 @@ public class Options { /** Holds all {@link Integer} {@link Option}s. */ private final Map> integerOptions = Collections.synchronizedMap(new HashMap>()); + /** Holds all {@link String} {@link Option}s. */ + private final Map> stringOptions = Collections.synchronizedMap(new HashMap>()); + /** * Adds a boolean option. * @@ -213,4 +216,30 @@ public class Options { return integerOptions.get(name); } + /** + * Adds a {@link String} {@link Option}. + * + * @param name + * The name of the option + * @param stringOption + * The option + * @return The given option + */ + public Option addStringOption(String name, Option stringOption) { + stringOptions.put(name, stringOption); + return stringOption; + } + + /** + * Returns a {@link String} {@link Option}. + * + * @param name + * The name of the string option to get + * @return The string option, or {@code null} if there is no option with the + * given name + */ + public Option getStringOption(String name) { + return stringOptions.get(name); + } + } diff --git a/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java b/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java index 4328f02..af4155d 100644 --- a/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java +++ b/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java @@ -310,6 +310,25 @@ public class SoneDownloader extends AbstractService { Profile profile = new Profile().setFirstName(profileFirstName).setMiddleName(profileMiddleName).setLastName(profileLastName); profile.setBirthDay(profileBirthDay).setBirthMonth(profileBirthMonth).setBirthYear(profileBirthYear); + /* parse profile fields. */ + SimpleXML profileFieldsXml = profileXml.getNode("fields"); + if (profileFieldsXml != null) { + for (SimpleXML fieldXml : profileFieldsXml.getNodes("field")) { + String fieldName = fieldXml.getValue("field-name", null); + String fieldValue = fieldXml.getValue("field-value", null); + if ((fieldName == null) || (fieldValue == null)) { + logger.log(Level.WARNING, "Downloaded profile field for Sone %s with missing data! Name: %s, Value: %s", new Object[] { sone, fieldName, fieldValue }); + return null; + } + try { + profile.addField(fieldName).setValue(fieldValue); + } catch (IllegalArgumentException iae1) { + logger.log(Level.WARNING, "Duplicate field: " + fieldName, iae1); + return null; + } + } + } + /* parse posts. */ SimpleXML postsXml = soneXml.getNode("posts"); Set posts = new HashSet(); diff --git a/src/main/java/net/pterodactylus/sone/core/UpdateChecker.java b/src/main/java/net/pterodactylus/sone/core/UpdateChecker.java index 0839cb9..02235d2 100644 --- a/src/main/java/net/pterodactylus/sone/core/UpdateChecker.java +++ b/src/main/java/net/pterodactylus/sone/core/UpdateChecker.java @@ -49,7 +49,7 @@ public class UpdateChecker { private static final String SONE_HOMEPAGE = "USK@nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI,DuQSUZiI~agF8c-6tjsFFGuZ8eICrzWCILB60nT8KKo,AQACAAE/sone/"; /** The current latest known edition. */ - private static final int LATEST_EDITION = 23; + private static final int LATEST_EDITION = 24; /** The Freenet interface. */ private final FreenetInterface freenetInterface; diff --git a/src/main/java/net/pterodactylus/sone/data/Fingerprintable.java b/src/main/java/net/pterodactylus/sone/data/Fingerprintable.java new file mode 100644 index 0000000..013e0b3 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/data/Fingerprintable.java @@ -0,0 +1,36 @@ +/* + * Sone - Fingerprintable.java - Copyright © 2011 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 . + */ + +package net.pterodactylus.sone.data; + +/** + * Interface for objects that can create a fingerprint of themselves, e.g. to + * detect modifications. The fingerprint should only contain original + * information; derived information should not be included. + * + * @author David ‘Bombe’ Roden + */ +public interface Fingerprintable { + + /** + * Returns the fingerprint of this object. + * + * @return The fingerprint of this object + */ + public String getFingerprint(); + +} diff --git a/src/main/java/net/pterodactylus/sone/data/Profile.java b/src/main/java/net/pterodactylus/sone/data/Profile.java index 7c29430..8d4306d 100644 --- a/src/main/java/net/pterodactylus/sone/data/Profile.java +++ b/src/main/java/net/pterodactylus/sone/data/Profile.java @@ -17,16 +17,20 @@ package net.pterodactylus.sone.data; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import net.pterodactylus.util.validation.Validation; + /** * A profile stores personal information about a {@link Sone}. All information * is optional and can be {@code null}. * * @author David ‘Bombe’ Roden */ -public class Profile { - - /** Whether the profile was modified. */ - private volatile boolean modified; +public class Profile implements Fingerprintable { /** The first name. */ private volatile String firstName; @@ -46,6 +50,9 @@ public class Profile { /** The year of the birth date. */ private volatile Integer birthYear; + /** Additional fields in the profile. */ + private final List fields = Collections.synchronizedList(new ArrayList()); + /** * Creates a new empty profile. */ @@ -69,6 +76,7 @@ public class Profile { this.birthDay = profile.birthDay; this.birthMonth = profile.birthMonth; this.birthYear = profile.birthYear; + this.fields.addAll(profile.fields); } // @@ -76,18 +84,6 @@ public class Profile { // /** - * Returns whether this profile was modified after creation. To clear the - * “is modified” flag you need to create a new profile from this one using - * the {@link #Profile(Profile)} constructor. - * - * @return {@code true} if this profile was modified after creation, - * {@code false} otherwise - */ - public boolean isModified() { - return modified; - } - - /** * Returns the first name. * * @return The first name @@ -104,7 +100,6 @@ public class Profile { * @return This profile (for method chaining) */ public Profile setFirstName(String firstName) { - modified |= ((firstName != null) && (!firstName.equals(this.firstName))) || (this.firstName != null); this.firstName = firstName; return this; } @@ -126,7 +121,6 @@ public class Profile { * @return This profile (for method chaining) */ public Profile setMiddleName(String middleName) { - modified |= ((middleName != null) && (!middleName.equals(this.middleName))) || (this.middleName != null); this.middleName = middleName; return this; } @@ -148,7 +142,6 @@ public class Profile { * @return This profile (for method chaining) */ public Profile setLastName(String lastName) { - modified |= ((lastName != null) && (!lastName.equals(this.lastName))) || (this.lastName != null); this.lastName = lastName; return this; } @@ -170,7 +163,6 @@ public class Profile { * @return This profile (for method chaining) */ public Profile setBirthDay(Integer birthDay) { - modified |= ((birthDay != null) && (!birthDay.equals(this.birthDay))) || (this.birthDay != null); this.birthDay = birthDay; return this; } @@ -192,7 +184,6 @@ public class Profile { * @return This profile (for method chaining) */ public Profile setBirthMonth(Integer birthMonth) { - modified |= ((birthMonth != null) && (!birthMonth.equals(this.birthMonth))) || (this.birthMonth != null); this.birthMonth = birthMonth; return this; } @@ -214,9 +205,292 @@ public class Profile { * @return This profile (for method chaining) */ public Profile setBirthYear(Integer birthYear) { - modified |= ((birthYear != null) && (!birthYear.equals(this.birthYear))) || (this.birthYear != null); this.birthYear = birthYear; return this; } + /** + * Returns the fields of this profile. + * + * @return The fields of this profile + */ + public List getFields() { + return new ArrayList(fields); + } + + /** + * Returns whether this profile contains the given field. + * + * @param field + * The field to check for + * @return {@code true} if this profile contains the field, false otherwise + */ + public boolean hasField(Field field) { + return fields.contains(field); + } + + /** + * Returns the field with the given ID. + * + * @param fieldId + * The ID of the field to get + * @return The field, or {@code null} if this profile does not contain a + * field with the given ID + */ + public Field getFieldById(String fieldId) { + Validation.begin().isNotNull("Field ID", fieldId).check(); + for (Field field : fields) { + if (field.getId().equals(fieldId)) { + return field; + } + } + return null; + } + + /** + * Returns the field with the given name. + * + * @param fieldName + * The name of the field to get + * @return The field, or {@code null} if this profile does not contain a + * field with the given name + */ + public Field getFieldByName(String fieldName) { + for (Field field : fields) { + if (field.getName().equals(fieldName)) { + return field; + } + } + return null; + } + + /** + * Appends a new field to the list of fields. + * + * @param fieldName + * The name of the new field + * @return The new field + * @throws IllegalArgumentException + * if the name is not valid + */ + public Field addField(String fieldName) throws IllegalArgumentException { + Validation.begin().isNotNull("Field Name", fieldName).check().isGreater("Field Name Length", fieldName.length(), 0).isNull("Field Name Unique", getFieldByName(fieldName)).check(); + @SuppressWarnings("synthetic-access") + Field field = new Field().setName(fieldName); + fields.add(field); + return field; + } + + /** + * Moves the given field up one position in the field list. The index of the + * field to move must be greater than {@code 0} (because you obviously can + * not move the first field further up). + * + * @param field + * The field to move up + */ + public void moveFieldUp(Field field) { + Validation.begin().isNotNull("Field", field).check().is("Field Existing", hasField(field)).isGreater("Field Index", getFieldIndex(field), 0).check(); + int fieldIndex = getFieldIndex(field); + fields.remove(field); + fields.add(fieldIndex - 1, field); + } + + /** + * Moves the given field down one position in the field list. The index of + * the field to move must be less than the index of the last field (because + * you obviously can not move the last field further down). + * + * @param field + * The field to move down + */ + public void moveFieldDown(Field field) { + Validation.begin().isNotNull("Field", field).check().is("Field Existing", hasField(field)).isLess("Field Index", getFieldIndex(field), fields.size() - 1).check(); + int fieldIndex = getFieldIndex(field); + fields.remove(field); + fields.add(fieldIndex + 1, field); + } + + /** + * Removes the given field. + * + * @param field + * The field to remove + */ + public void removeField(Field field) { + Validation.begin().isNotNull("Field", field).check().is("Field Existing", hasField(field)).check(); + fields.remove(field); + } + + // + // PRIVATE METHODS + // + + /** + * Returns the index of the field with the given name. + * + * @param field + * The name of the field + * @return The index of the field, or {@code -1} if there is no field with + * the given name + */ + private int getFieldIndex(Field field) { + return fields.indexOf(field); + } + + // + // INTERFACE Fingerprintable + // + + /** + * {@inheritDoc} + */ + @Override + public String getFingerprint() { + StringBuilder fingerprint = new StringBuilder(); + fingerprint.append("Profile("); + if (firstName != null) { + fingerprint.append("FirstName(").append(firstName).append(')'); + } + if (middleName != null) { + fingerprint.append("MiddleName(").append(middleName).append(')'); + } + if (lastName != null) { + fingerprint.append("LastName(").append(lastName).append(')'); + } + if (birthDay != null) { + fingerprint.append("BirthDay(").append(birthDay).append(')'); + } + if (birthMonth != null) { + fingerprint.append("BirthMonth(").append(birthMonth).append(')'); + } + if (birthYear != null) { + fingerprint.append("BirthYear(").append(birthYear).append(')'); + } + fingerprint.append("ContactInformation("); + for (Field field : fields) { + fingerprint.append(field.getName()).append('(').append(field.getValue()).append(')'); + } + fingerprint.append(")"); + fingerprint.append(")"); + + return fingerprint.toString(); + } + + /** + * Container for a profile field. + * + * @author David ‘Bombe’ Roden + */ + public class Field { + + /** The ID of the field. */ + private final String id; + + /** The name of the field. */ + private String name; + + /** The value of the field. */ + private String value; + + /** + * Creates a new field with a random ID. + */ + private Field() { + this(UUID.randomUUID().toString()); + } + + /** + * Creates a new field with the given ID. + * + * @param id + * The ID of the field + */ + private Field(String id) { + Validation.begin().isNotNull("Field ID", id).check(); + this.id = id; + } + + /** + * Returns the ID of this field. + * + * @return The ID of this field + */ + public String getId() { + return id; + } + + /** + * Returns the name of this field. + * + * @return The name of this field + */ + public String getName() { + return name; + } + + /** + * Sets the name of this field. The name must not be {@code null} and + * must not match any other fields in this profile but my match the name + * of this field. + * + * @param name + * The new name of this field + * @return This field + */ + public Field setName(String name) { + Validation.begin().isNotNull("Field Name", name).check().is("Field Unique", (getFieldByName(name) == null) || equals(getFieldByName(name))).check(); + this.name = name; + return this; + } + + /** + * Returns the value of this field. + * + * @return The value of this field + */ + public String getValue() { + return value; + } + + /** + * Sets the value of this field. While {@code null} is allowed, no + * guarantees are made that {@code null} values are correctly persisted + * across restarts of the plugin! + * + * @param value + * The new value of this field + * @return This field + */ + public Field setValue(String value) { + this.value = value; + return this; + } + + // + // OBJECT METHODS + // + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (!(object instanceof Field)) { + return false; + } + Field field = (Field) object; + return id.equals(field.id); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return id.hashCode(); + } + + } + } diff --git a/src/main/java/net/pterodactylus/sone/data/Sone.java b/src/main/java/net/pterodactylus/sone/data/Sone.java index 7d4f7ef..03dff75 100644 --- a/src/main/java/net/pterodactylus/sone/data/Sone.java +++ b/src/main/java/net/pterodactylus/sone/data/Sone.java @@ -40,7 +40,7 @@ import freenet.keys.FreenetURI; * * @author David ‘Bombe’ Roden */ -public class Sone { +public class Sone implements Fingerprintable { /** comparator that sorts Sones by their nice name. */ public static final Comparator NICE_NAME_COMPARATOR = new Comparator() { @@ -580,36 +580,17 @@ public class Sone { return this; } + // + // FINGERPRINTABLE METHODS + // + /** - * Returns a fingerprint of this Sone. The fingerprint only depends on data - * that is actually stored when a Sone is inserted. The fingerprint can be - * used to detect changes in Sone data and can also be used to detect if - * previous changes are reverted. - * - * @return The fingerprint of this Sone + * {@inheritDoc} */ + @Override public synchronized String getFingerprint() { StringBuilder fingerprint = new StringBuilder(); - fingerprint.append("Profile("); - if (profile.getFirstName() != null) { - fingerprint.append("FirstName(").append(profile.getFirstName()).append(')'); - } - if (profile.getMiddleName() != null) { - fingerprint.append("MiddleName(").append(profile.getMiddleName()).append(')'); - } - if (profile.getLastName() != null) { - fingerprint.append("LastName(").append(profile.getLastName()).append(')'); - } - if (profile.getBirthDay() != null) { - fingerprint.append("BirthDay(").append(profile.getBirthDay()).append(')'); - } - if (profile.getBirthMonth() != null) { - fingerprint.append("BirthMonth(").append(profile.getBirthMonth()).append(')'); - } - if (profile.getBirthYear() != null) { - fingerprint.append("BirthYear(").append(profile.getBirthYear()).append(')'); - } - fingerprint.append(")"); + fingerprint.append(profile.getFingerprint()); fingerprint.append("Posts("); for (Post post : getPosts()) { diff --git a/src/main/java/net/pterodactylus/sone/freenet/plugin/ConnectorListener.java b/src/main/java/net/pterodactylus/sone/freenet/plugin/ConnectorListener.java new file mode 100644 index 0000000..71710e4 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/freenet/plugin/ConnectorListener.java @@ -0,0 +1,51 @@ +/* + * Sone - ConnectorListener.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 . + */ + +package net.pterodactylus.sone.freenet.plugin; + +import java.util.EventListener; + + +import freenet.support.SimpleFieldSet; +import freenet.support.api.Bucket; + +/** + * Interface for objects that want to be notified if a {@link PluginConnector} + * receives a reply from a plugin. As a connection listener is always + * {@link PluginConnector#addConnectorListener(String, String, ConnectorListener) + * added} for a specific plugin, it will always be notified for replies from the + * correct plugin (unless you register the same listener for multiple + * plugins—which you subsequently should not do). + * + * @author David ‘Bombe’ Roden + */ +public interface ConnectorListener extends EventListener { + + /** + * A reply was received from the plugin this connection listener was added + * for. + * + * @param pluginConnector + * The plugin connector that received the reply + * @param fields + * The fields of the reply + * @param data + * The data of the reply (may be null) + */ + public void receivedReply(PluginConnector pluginConnector, SimpleFieldSet fields, Bucket data); + +} diff --git a/src/main/java/net/pterodactylus/sone/freenet/plugin/ConnectorListenerManager.java b/src/main/java/net/pterodactylus/sone/freenet/plugin/ConnectorListenerManager.java new file mode 100644 index 0000000..7021332 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/freenet/plugin/ConnectorListenerManager.java @@ -0,0 +1,60 @@ +/* + * Sone - ConnectorListenerManager.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 . + */ + +package net.pterodactylus.sone.freenet.plugin; + +import net.pterodactylus.util.event.AbstractListenerManager; +import freenet.support.SimpleFieldSet; +import freenet.support.api.Bucket; + +/** + * Manages {@link ConnectorListener}s and fire events. + * + * @author David ‘Bombe’ Roden + */ +public class ConnectorListenerManager extends AbstractListenerManager { + + /** + * Creates a new manager for {@link ConnectorListener}s. + * + * @param pluginConnector + * The plugin connector that is the source for all events + */ + public ConnectorListenerManager(PluginConnector pluginConnector) { + super(pluginConnector); + } + + // + // ACTIONS + // + + /** + * Notifies all registered listeners that a reply from the plugin was + * received. + * + * @param fields + * The fields of the reply + * @param data + * The data of the reply (may be null) + */ + public void fireReceivedReply(SimpleFieldSet fields, Bucket data) { + for (ConnectorListener connectorListener : getListeners()) { + connectorListener.receivedReply(getSource(), fields, data); + } + } + +} diff --git a/src/main/java/net/pterodactylus/sone/freenet/plugin/PluginConnector.java b/src/main/java/net/pterodactylus/sone/freenet/plugin/PluginConnector.java new file mode 100644 index 0000000..e7bf828 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/freenet/plugin/PluginConnector.java @@ -0,0 +1,203 @@ +/* + * Sone - PluginConnector.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 . + */ + +package net.pterodactylus.sone.freenet.plugin; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import net.pterodactylus.util.collection.Pair; +import freenet.pluginmanager.FredPluginTalker; +import freenet.pluginmanager.PluginNotFoundException; +import freenet.pluginmanager.PluginRespirator; +import freenet.pluginmanager.PluginTalker; +import freenet.support.SimpleFieldSet; +import freenet.support.api.Bucket; + +/** + * Interface for talking to other plugins. Other plugins are identified by their + * name and a unique connection identifier. + * + * @author David ‘Bombe’ Roden + */ +public class PluginConnector implements FredPluginTalker { + + /** The plugin respirator. */ + private final PluginRespirator pluginRespirator; + + /** Connector listener managers for all plugin connections. */ + private final Map, ConnectorListenerManager> connectorListenerManagers = Collections.synchronizedMap(new HashMap, ConnectorListenerManager>()); + + /** + * Creates a new plugin connector. + * + * @param pluginRespirator + * The plugin respirator + */ + public PluginConnector(PluginRespirator pluginRespirator) { + this.pluginRespirator = pluginRespirator; + } + + // + // LISTENER MANAGEMENT + // + + /** + * Adds a connection listener for the given plugin connection. + * + * @param pluginName + * The name of the plugin + * @param identifier + * The identifier of the connection + * @param connectorListener + * The listener to add + */ + public void addConnectorListener(String pluginName, String identifier, ConnectorListener connectorListener) { + getConnectorListenerManager(pluginName, identifier).addListener(connectorListener); + } + + /** + * Removes a connection listener for the given plugin connection. + * + * @param pluginName + * The name of the plugin + * @param identifier + * The identifier of the connection + * @param connectorListener + * The listener to remove + */ + public void removeConnectorListener(String pluginName, String identifier, ConnectorListener connectorListener) { + getConnectorListenerManager(pluginName, identifier).removeListener(connectorListener); + } + + // + // ACTIONS + // + + /** + * Sends a request to the given plugin. + * + * @param pluginName + * The name of the plugin + * @param identifier + * The identifier of the connection + * @param fields + * The fields of the message + * @throws PluginException + * if the plugin can not be found + */ + public void sendRequest(String pluginName, String identifier, SimpleFieldSet fields) throws PluginException { + sendRequest(pluginName, identifier, fields, null); + } + + /** + * Sends a request to the given plugin. + * + * @param pluginName + * The name of the plugin + * @param identifier + * The identifier of the connection + * @param fields + * The fields of the message + * @param data + * The payload of the message (may be null) + * @throws PluginException + * if the plugin can not be found + */ + public void sendRequest(String pluginName, String identifier, SimpleFieldSet fields, Bucket data) throws PluginException { + getPluginTalker(pluginName, identifier).send(fields, data); + } + + // + // PRIVATE METHODS + // + + /** + * Returns the connection listener manager for the given plugin connection, + * creating a new one if none does exist yet. + * + * @param pluginName + * The name of the plugin + * @param identifier + * The identifier of the connection + * @return The connection listener manager + */ + private ConnectorListenerManager getConnectorListenerManager(String pluginName, String identifier) { + return getConnectorListenerManager(pluginName, identifier, true); + } + + /** + * Returns the connection listener manager for the given plugin connection, + * optionally creating a new one if none does exist yet. + * + * @param pluginName + * The name of the plugin + * @param identifier + * The identifier of the connection + * @param create + * {@code true} to create a new manager if there is none, + * {@code false} to return {@code null} in that case + * @return The connection listener manager, or {@code null} if none existed + * and {@code create} is {@code false} + */ + private ConnectorListenerManager getConnectorListenerManager(String pluginName, String identifier, boolean create) { + ConnectorListenerManager connectorListenerManager = connectorListenerManagers.get(new Pair(pluginName, identifier)); + if (create && (connectorListenerManager == null)) { + connectorListenerManager = new ConnectorListenerManager(this); + connectorListenerManagers.put(new Pair(pluginName, identifier), connectorListenerManager); + } + return connectorListenerManager; + } + + /** + * Returns the plugin talker for the given plugin connection. + * + * @param pluginName + * The name of the plugin + * @param identifier + * The identifier of the connection + * @return The plugin talker + * @throws PluginException + * if the plugin can not be found + */ + private PluginTalker getPluginTalker(String pluginName, String identifier) throws PluginException { + try { + return pluginRespirator.getPluginTalker(this, pluginName, identifier); + } catch (PluginNotFoundException pnfe1) { + throw new PluginException(pnfe1); + } + } + + // + // INTERFACE FredPluginTalker + // + + /** + * {@inheritDoc} + */ + @Override + public void onReply(String pluginName, String identifier, SimpleFieldSet params, Bucket data) { + ConnectorListenerManager connectorListenerManager = getConnectorListenerManager(pluginName, identifier, false); + if (connectorListenerManager == null) { + /* we don’t care about events for this plugin. */ + return; + } + connectorListenerManager.fireReceivedReply(params, data); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/freenet/plugin/PluginException.java b/src/main/java/net/pterodactylus/sone/freenet/plugin/PluginException.java new file mode 100644 index 0000000..0e1af2b --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/freenet/plugin/PluginException.java @@ -0,0 +1,68 @@ +/* + * Sone - PluginException.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 . + */ + +package net.pterodactylus.sone.freenet.plugin; + +import net.pterodactylus.sone.freenet.wot.WebOfTrustException; + +/** + * Exception that signals an error when communicating with a plugin. + * + * @author David ‘Bombe’ Roden + */ +public class PluginException extends WebOfTrustException { + + /** + * Creates a new plugin exception. + */ + public PluginException() { + super(); + } + + /** + * Creates a new plugin exception. + * + * @param message + * The message of the exception + */ + public PluginException(String message) { + super(message); + } + + /** + * Creates a new plugin exception. + * + * @param cause + * The cause of the exception + */ + public PluginException(Throwable cause) { + super(cause); + } + + /** + * Creates a new plugin exception. + * + * @param message + * The message of the exception + * @param cause + * The cause of the exception + */ + public PluginException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/ConnectorListener.java b/src/main/java/net/pterodactylus/sone/freenet/wot/ConnectorListener.java deleted file mode 100644 index cf1d1e3..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/ConnectorListener.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Sone - ConnectorListener.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 . - */ - -package net.pterodactylus.sone.freenet.wot; - -import java.util.EventListener; - -import freenet.support.SimpleFieldSet; -import freenet.support.api.Bucket; - -/** - * Interface for objects that want to be notified if a {@link PluginConnector} - * receives a reply from a plugin. As a connection listener is always - * {@link PluginConnector#addConnectorListener(String, String, ConnectorListener) - * added} for a specific plugin, it will always be notified for replies from the - * correct plugin (unless you register the same listener for multiple - * plugins—which you subsequently should not do). - * - * @author David ‘Bombe’ Roden - */ -public interface ConnectorListener extends EventListener { - - /** - * A reply was received from the plugin this connection listener was added - * for. - * - * @param pluginConnector - * The plugin connector that received the reply - * @param fields - * The fields of the reply - * @param data - * The data of the reply (may be null) - */ - public void receivedReply(PluginConnector pluginConnector, SimpleFieldSet fields, Bucket data); - -} diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/ConnectorListenerManager.java b/src/main/java/net/pterodactylus/sone/freenet/wot/ConnectorListenerManager.java deleted file mode 100644 index 3962eef..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/ConnectorListenerManager.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Sone - ConnectorListenerManager.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 . - */ - -package net.pterodactylus.sone.freenet.wot; - -import net.pterodactylus.util.event.AbstractListenerManager; -import freenet.support.SimpleFieldSet; -import freenet.support.api.Bucket; - -/** - * Manages {@link ConnectorListener}s and fire events. - * - * @author David ‘Bombe’ Roden - */ -public class ConnectorListenerManager extends AbstractListenerManager { - - /** - * Creates a new manager for {@link ConnectorListener}s. - * - * @param pluginConnector - * The plugin connector that is the source for all events - */ - public ConnectorListenerManager(PluginConnector pluginConnector) { - super(pluginConnector); - } - - // - // ACTIONS - // - - /** - * Notifies all registered listeners that a reply from the plugin was - * received. - * - * @param fields - * The fields of the reply - * @param data - * The data of the reply (may be null) - */ - public void fireReceivedReply(SimpleFieldSet fields, Bucket data) { - for (ConnectorListener connectorListener : getListeners()) { - connectorListener.receivedReply(getSource(), fields, data); - } - } - -} diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/DefaultIdentity.java b/src/main/java/net/pterodactylus/sone/freenet/wot/DefaultIdentity.java new file mode 100644 index 0000000..3f4e66a --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/freenet/wot/DefaultIdentity.java @@ -0,0 +1,303 @@ +/* + * Sone - DefaultIdentity.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 . + */ + +package net.pterodactylus.sone.freenet.wot; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import net.pterodactylus.sone.freenet.plugin.PluginException; +import net.pterodactylus.util.cache.CacheException; +import net.pterodactylus.util.cache.CacheItem; +import net.pterodactylus.util.cache.DefaultCacheItem; +import net.pterodactylus.util.cache.MemoryCache; +import net.pterodactylus.util.cache.ValueRetriever; +import net.pterodactylus.util.cache.WritableCache; +import net.pterodactylus.util.collection.TimedMap; +import net.pterodactylus.util.logging.Logging; + +/** + * A Web of Trust identity. + * + * @author David ‘Bombe’ Roden + */ +public class DefaultIdentity implements Identity { + + /** The logger. */ + private static final Logger logger = Logging.getLogger(DefaultIdentity.class); + + /** The web of trust connector. */ + private final WebOfTrustConnector webOfTrustConnector; + + /** The ID of the identity. */ + private final String id; + + /** The nickname of the identity. */ + private final String nickname; + + /** The request URI of the identity. */ + private final String requestUri; + + /** The contexts of the identity. */ + private final Set contexts = Collections.synchronizedSet(new HashSet()); + + /** The properties of the identity. */ + private final Map properties = Collections.synchronizedMap(new HashMap()); + + /** Cached trust. */ + private final WritableCache trustCache = new MemoryCache(new ValueRetriever() { + + @Override + @SuppressWarnings("synthetic-access") + public CacheItem retrieve(OwnIdentity ownIdentity) throws CacheException { + try { + return new DefaultCacheItem(webOfTrustConnector.getTrust(ownIdentity, DefaultIdentity.this)); + } catch (PluginException pe1) { + throw new CacheException("Could not retrieve trust for OwnIdentity: " + ownIdentity, pe1); + } + } + + }, new TimedMap>(60000)); + + /** + * Creates a new identity. + * + * @param webOfTrustConnector + * The web of trust connector + * @param id + * The ID of the identity + * @param nickname + * The nickname of the identity + * @param requestUri + * The request URI of the identity + */ + public DefaultIdentity(WebOfTrustConnector webOfTrustConnector, String id, String nickname, String requestUri) { + this.webOfTrustConnector = webOfTrustConnector; + this.id = id; + this.nickname = nickname; + this.requestUri = requestUri; + } + + // + // ACCESSORS + // + + /** + * {@inheritDoc} + */ + @Override + public String getId() { + return id; + } + + /** + * {@inheritDoc} + */ + @Override + public String getNickname() { + return nickname; + } + + /** + * {@inheritDoc} + */ + @Override + public String getRequestUri() { + return requestUri; + } + + /** + * {@inheritDoc} + */ + @Override + public Set getContexts() { + return Collections.unmodifiableSet(contexts); + } + + /** + * Sets the contexts of this identity. + *

+ * This method is only called by the {@link IdentityManager}. + * + * @param contexts + * The contexts to set + */ + void setContextsPrivate(Set contexts) { + this.contexts.clear(); + this.contexts.addAll(contexts); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean hasContext(String context) { + return contexts.contains(context); + } + + /** + * Adds the given context to this identity. + *

+ * This method is only called by the {@link IdentityManager}. + * + * @param context + * The context to add + */ + void addContextPrivate(String context) { + contexts.add(context); + } + + /** + * Removes the given context from this identity. + *

+ * This method is only called by the {@link IdentityManager}. + * + * @param context + * The context to remove + */ + public void removeContextPrivate(String context) { + contexts.remove(context); + } + + /** + * {@inheritDoc} + */ + @Override + public Map getProperties() { + synchronized (properties) { + return Collections.unmodifiableMap(properties); + } + } + + /** + * Sets all properties of this identity. + *

+ * This method is only called by the {@link IdentityManager}. + * + * @param properties + * The new properties of this identity + */ + void setPropertiesPrivate(Map properties) { + synchronized (this.properties) { + this.properties.clear(); + this.properties.putAll(properties); + } + } + + /** + * Sets the property with the given name to the given value. + *

+ * This method is only called by the {@link IdentityManager}. + * + * @param name + * The name of the property + * @param value + * The value of the property + */ + void setPropertyPrivate(String name, String value) { + synchronized (properties) { + properties.put(name, value); + } + } + + /** + * {@inheritDoc} + */ + @Override + public String getProperty(String name) { + synchronized (properties) { + return properties.get(name); + } + } + + /** + * Removes the property with the given name. + *

+ * This method is only called by the {@link IdentityManager}. + * + * @param name + * The name of the property to remove + */ + void removePropertyPrivate(String name) { + synchronized (properties) { + properties.remove(name); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Trust getTrust(OwnIdentity ownIdentity) { + try { + return trustCache.get(ownIdentity); + } catch (CacheException ce1) { + logger.log(Level.WARNING, "Could not get trust for OwnIdentity: " + ownIdentity, ce1); + return null; + } + } + + /** + * Sets the trust received for this identity by the given own identity. + * + * @param ownIdentity + * The own identity that gives the trust + * @param trust + * The trust received for this identity + */ + void setTrustPrivate(OwnIdentity ownIdentity, Trust trust) { + trustCache.put(ownIdentity, trust); + } + + // + // OBJECT METHODS + // + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return id.hashCode(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (!(object instanceof DefaultIdentity)) { + return false; + } + DefaultIdentity identity = (DefaultIdentity) object; + return identity.id.equals(id); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return getClass().getSimpleName() + "[id=" + id + ",nickname=" + nickname + ",contexts=" + contexts + ",properties=" + properties + "]"; + } + +} diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentity.java b/src/main/java/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentity.java new file mode 100644 index 0000000..ab96756 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentity.java @@ -0,0 +1,170 @@ +/* + * Sone - DefaultOwnIdentity.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 . + */ + +package net.pterodactylus.sone.freenet.wot; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import net.pterodactylus.util.validation.Validation; + +/** + * An own identity is an identity that the owner of the node has full control + * over. + * + * @author David ‘Bombe’ Roden + */ +public class DefaultOwnIdentity extends DefaultIdentity implements OwnIdentity { + + /** The identity manager. */ + private final WebOfTrustConnector webOfTrustConnector; + + /** The insert URI of the identity. */ + private final String insertUri; + + /** + * Creates a new own identity. + * + * @param webOfTrustConnector + * The identity manager + * @param id + * The ID of the identity + * @param nickname + * The nickname of the identity + * @param requestUri + * The request URI of the identity + * @param insertUri + * The insert URI of the identity + */ + public DefaultOwnIdentity(WebOfTrustConnector webOfTrustConnector, String id, String nickname, String requestUri, String insertUri) { + super(webOfTrustConnector, id, nickname, requestUri); + this.webOfTrustConnector = webOfTrustConnector; + this.insertUri = insertUri; + } + + // + // ACCESSORS + // + + /** + * {@inheritDoc} + */ + @Override + public String getInsertUri() { + return insertUri; + } + + /** + * {@inheritDoc} + */ + @Override + public void addContext(String context) throws WebOfTrustException { + webOfTrustConnector.addContext(this, context); + addContextPrivate(context); + } + + /** + * {@inheritDoc} + */ + @Override + public void removeContext(String context) throws WebOfTrustException { + webOfTrustConnector.removeContext(this, context); + removeContextPrivate(context); + } + + /** + * {@inheritDoc} + */ + @Override + public void setContexts(Set contexts) throws WebOfTrustException { + for (String context : getContexts()) { + if (!contexts.contains(context)) { + webOfTrustConnector.removeContext(this, context); + } + } + for (String context : contexts) { + if (!getContexts().contains(context)) { + webOfTrustConnector.addContext(this, context); + } + } + setContextsPrivate(contexts); + } + + /** + * {@inheritDoc} + */ + @Override + public void setProperty(String name, String value) throws WebOfTrustException { + webOfTrustConnector.setProperty(this, name, value); + setPropertyPrivate(name, value); + } + + /** + * {@inheritDoc} + */ + @Override + public void removeProperty(String name) throws WebOfTrustException { + webOfTrustConnector.removeProperty(this, name); + removePropertyPrivate(name); + } + + /** + * {@inheritDoc} + */ + @Override + public void setProperties(Map properties) throws WebOfTrustException { + for (Entry oldProperty : getProperties().entrySet()) { + if (!properties.containsKey(oldProperty.getKey())) { + webOfTrustConnector.removeProperty(this, oldProperty.getKey()); + } else { + webOfTrustConnector.setProperty(this, oldProperty.getKey(), properties.get(oldProperty.getKey())); + } + } + for (Entry newProperty : properties.entrySet()) { + if (!getProperties().containsKey(newProperty.getKey())) { + webOfTrustConnector.setProperty(this, newProperty.getKey(), newProperty.getValue()); + } + } + setPropertiesPrivate(properties); + } + + /** + * {@inheritDoc} + */ + @Override + public void setTrust(Identity target, int trustValue, String comment) throws WebOfTrustException { + Validation.begin().isNotNull("Trust Target", target).isNotNull("Trust Comment", comment).isLessOrEqual("Trust Value", trustValue, 100).isGreaterOrEqual("Trust Value", trustValue, -100).check(); + webOfTrustConnector.setTrust(this, target, trustValue, comment); + if (target instanceof DefaultIdentity) { + ((DefaultIdentity) target).setTrustPrivate(this, new Trust(trustValue, trustValue, 0)); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void removeTrust(Identity target) throws WebOfTrustException { + Validation.begin().isNotNull("Trust Target", target).check(); + webOfTrustConnector.removeTrust(this, target); + if (target instanceof DefaultIdentity) { + ((DefaultIdentity) target).setTrustPrivate(this, new Trust(null, null, null)); + } + } + +} diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/Identity.java b/src/main/java/net/pterodactylus/sone/freenet/wot/Identity.java index 5816b47..1cf1644 100644 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/Identity.java +++ b/src/main/java/net/pterodactylus/sone/freenet/wot/Identity.java @@ -17,102 +17,45 @@ package net.pterodactylus.sone.freenet.wot; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.Set; /** - * A Web of Trust identity. + * Interface for web of trust identities, defining all functions that can be + * performed on an identity. The identity is the main entry point for identity + * management. * * @author David ‘Bombe’ Roden */ -public class Identity { - - /** The ID of the identity. */ - private final String id; - - /** The nickname of the identity. */ - private final String nickname; - - /** The request URI of the identity. */ - private final String requestUri; - - /** The contexts of the identity. */ - private final Set contexts = Collections.synchronizedSet(new HashSet()); - - /** The properties of the identity. */ - private final Map properties = Collections.synchronizedMap(new HashMap()); - - /** - * Creates a new identity. - * - * @param id - * The ID of the identity - * @param nickname - * The nickname of the identity - * @param requestUri - * The request URI of the identity - */ - public Identity(String id, String nickname, String requestUri) { - this.id = id; - this.nickname = nickname; - this.requestUri = requestUri; - } - - // - // ACCESSORS - // +public interface Identity { /** * Returns the ID of the identity. * * @return The ID of the identity */ - public String getId() { - return id; - } + public String getId(); /** * Returns the nickname of the identity. * * @return The nickname of the identity */ - public String getNickname() { - return nickname; - } + public String getNickname(); /** * Returns the request URI of the identity. * * @return The request URI of the identity */ - public String getRequestUri() { - return requestUri; - } + public String getRequestUri(); /** * Returns all contexts of this identity. * * @return All contexts of this identity */ - public Set getContexts() { - return Collections.unmodifiableSet(contexts); - } - - /** - * Sets all contexts of this identity. - *

- * This method is only called by the {@link IdentityManager}. - * - * @param contexts - * All contexts of the identity - */ - void setContexts(Set contexts) { - this.contexts.clear(); - this.contexts.addAll(contexts); - } + public Set getContexts(); /** * Returns whether this identity has the given context. @@ -122,75 +65,14 @@ public class Identity { * @return {@code true} if this identity has the given context, * {@code false} otherwise */ - public boolean hasContext(String context) { - return contexts.contains(context); - } - - /** - * Adds the given context to this identity. - *

- * This method is only called by the {@link IdentityManager}. - * - * @param context - * The context to add - */ - void addContext(String context) { - contexts.add(context); - } - - /** - * Removes the given context from this identity. - *

- * This method is only called by the {@link IdentityManager}. - * - * @param context - * The context to remove - */ - void removeContext(String context) { - contexts.remove(context); - } + public boolean hasContext(String context); /** * Returns all properties of this identity. * * @return All properties of this identity */ - public Map getProperties() { - synchronized (properties) { - return Collections.unmodifiableMap(properties); - } - } - - /** - * Sets all properties of this identity. - *

- * This method is only called by the {@link IdentityManager}. - * - * @param properties - * The new properties of this identity - */ - void setProperties(Map properties) { - synchronized (this.properties) { - this.properties.clear(); - this.properties.putAll(properties); - } - } - - /** - * Sets the property with the given name to the given value. - *

- * This method is only called by the {@link IdentityManager}. - * - * @param name - * The name of the property - * @param value - * The value of the property - */ - void setProperty(String name, String value) { - synchronized (properties) { - properties.put(name, value); - } - } + public Map getProperties(); /** * Returns the value of the property with the given name. @@ -199,56 +81,19 @@ public class Identity { * The name of the property * @return The value of the property */ - public String getProperty(String name) { - synchronized (properties) { - return properties.get(name); - } - } + public String getProperty(String name); /** - * Removes the property with the given name. - *

- * This method is only called by the {@link IdentityManager}. + * Retrieves the trust that this identity receives from the given own + * identity. If this identity is not in the own identity’s trust tree, a + * {@link Trust} is returned that has all its elements set to {@code null}. + * If the trust can not be retrieved, {@code null} is returned. * - * @param name - * The name of the property to remove - */ - void removeProperty(String name) { - synchronized (properties) { - properties.remove(name); - } - } - - // - // OBJECT METHODS - // - - /** - * {@inheritDoc} - */ - @Override - public int hashCode() { - return id.hashCode(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean equals(Object object) { - if (!(object instanceof Identity)) { - return false; - } - Identity identity = (Identity) object; - return identity.id.equals(id); - } - - /** - * {@inheritDoc} + * @param ownIdentity + * The own identity to get the trust for + * @return The trust assigned to this identity, or {@code null} if the trust + * could not be retrieved */ - @Override - public String toString() { - return getClass().getSimpleName() + "[id=" + id + ",nickname=" + nickname + ",contexts=" + contexts + ",properties=" + properties + "]"; - } + public Trust getTrust(OwnIdentity ownIdentity); } diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityListener.java b/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityListener.java index 64ea61f..3721f49 100644 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityListener.java +++ b/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityListener.java @@ -47,25 +47,31 @@ public interface IdentityListener extends EventListener { /** * Notifies a listener that a new identity was discovered. * + * @param ownIdentity + * The own identity at the root of the trust tree * @param identity * The new identity */ - public void identityAdded(Identity identity); + public void identityAdded(OwnIdentity ownIdentity, Identity identity); /** * Notifies a listener that some properties of the identity have changed. * + * @param ownIdentity + * The own identity at the root of the trust tree * @param identity * The updated identity */ - public void identityUpdated(Identity identity); + public void identityUpdated(OwnIdentity ownIdentity, Identity identity); /** * Notifies a listener that an identity has gone away. * + * @param ownIdentity + * The own identity at the root of the trust tree * @param identity * The disappeared identity */ - public void identityRemoved(Identity identity); + public void identityRemoved(OwnIdentity ownIdentity, Identity identity); } diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityListenerManager.java b/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityListenerManager.java index c08fd16..c6ea783 100644 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityListenerManager.java +++ b/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityListenerManager.java @@ -68,39 +68,45 @@ public class IdentityListenerManager extends AbstractListenerManager oldIdentities = Collections.emptyMap(); + Map> oldIdentities = Collections.emptyMap(); while (!shouldStop()) { - Map currentIdentities = new HashMap(); + Map> currentIdentities = new HashMap>(); Map currentOwnIdentities = new HashMap(); - /* get all identities with the wanted context from WoT. */ - Set ownIdentities; try { - ownIdentities = webOfTrustConnector.loadAllOwnIdentities(); + /* get all identities with the wanted context from WoT. */ + Set ownIdentities = webOfTrustConnector.loadAllOwnIdentities(); /* check for changes. */ for (OwnIdentity ownIdentity : ownIdentities) { @@ -271,76 +191,80 @@ public class IdentityManager extends AbstractService { checkOwnIdentities(currentOwnIdentities); /* now filter for context and get all identities. */ - currentOwnIdentities.clear(); for (OwnIdentity ownIdentity : ownIdentities) { if ((context != null) && !ownIdentity.hasContext(context)) { continue; } - currentOwnIdentities.put(ownIdentity.getId(), ownIdentity); - for (Identity identity : webOfTrustConnector.loadTrustedIdentities(ownIdentity, context)) { - currentIdentities.put(identity.getId(), identity); - } - } - /* find removed identities. */ - for (Identity oldIdentity : oldIdentities.values()) { - if (!currentIdentities.containsKey(oldIdentity.getId())) { - identityListenerManager.fireIdentityRemoved(oldIdentity); + Set trustedIdentities = webOfTrustConnector.loadTrustedIdentities(ownIdentity, context); + Map identities = new HashMap(); + currentIdentities.put(ownIdentity, identities); + for (Identity identity : trustedIdentities) { + identities.put(identity.getId(), identity); } - } - /* find new identities. */ - for (Identity currentIdentity : currentIdentities.values()) { - if (!oldIdentities.containsKey(currentIdentity.getId())) { - identityListenerManager.fireIdentityAdded(currentIdentity); + /* find new identities. */ + for (Identity currentIdentity : currentIdentities.get(ownIdentity).values()) { + if (!oldIdentities.containsKey(ownIdentity) || !oldIdentities.get(ownIdentity).containsKey(currentIdentity.getId())) { + identityListenerManager.fireIdentityAdded(ownIdentity, currentIdentity); + } } - } - /* check for changes in the contexts. */ - for (Identity oldIdentity : oldIdentities.values()) { - if (!currentIdentities.containsKey(oldIdentity.getId())) { - continue; - } - Identity newIdentity = currentIdentities.get(oldIdentity.getId()); - Set oldContexts = oldIdentity.getContexts(); - Set newContexts = newIdentity.getContexts(); - if (oldContexts.size() != newContexts.size()) { - identityListenerManager.fireIdentityUpdated(newIdentity); - continue; - } - for (String oldContext : oldContexts) { - if (!newContexts.contains(oldContext)) { - identityListenerManager.fireIdentityUpdated(newIdentity); - break; + /* find removed identities. */ + if (oldIdentities.containsKey(ownIdentity)) { + for (Identity oldIdentity : oldIdentities.get(ownIdentity).values()) { + if (!currentIdentities.get(ownIdentity).containsKey(oldIdentity.getId())) { + identityListenerManager.fireIdentityRemoved(ownIdentity, oldIdentity); + } } - } - } - /* check for changes in the properties. */ - for (Identity oldIdentity : oldIdentities.values()) { - if (!currentIdentities.containsKey(oldIdentity.getId())) { - continue; - } - Identity newIdentity = currentIdentities.get(oldIdentity.getId()); - Map oldProperties = oldIdentity.getProperties(); - Map newProperties = newIdentity.getProperties(); - if (oldProperties.size() != newProperties.size()) { - identityListenerManager.fireIdentityUpdated(newIdentity); - continue; - } - for (Entry oldProperty : oldProperties.entrySet()) { - if (!newProperties.containsKey(oldProperty.getKey()) || !newProperties.get(oldProperty.getKey()).equals(oldProperty.getValue())) { - identityListenerManager.fireIdentityUpdated(newIdentity); - break; + /* check for changes in the contexts. */ + for (Identity oldIdentity : oldIdentities.get(ownIdentity).values()) { + if (!currentIdentities.get(ownIdentity).containsKey(oldIdentity.getId())) { + continue; + } + Identity newIdentity = currentIdentities.get(ownIdentity).get(oldIdentity.getId()); + Set oldContexts = oldIdentity.getContexts(); + Set newContexts = newIdentity.getContexts(); + if (oldContexts.size() != newContexts.size()) { + identityListenerManager.fireIdentityUpdated(ownIdentity, newIdentity); + continue; + } + for (String oldContext : oldContexts) { + if (!newContexts.contains(oldContext)) { + identityListenerManager.fireIdentityUpdated(ownIdentity, newIdentity); + break; + } + } + } + + /* check for changes in the properties. */ + for (Identity oldIdentity : oldIdentities.get(ownIdentity).values()) { + if (!currentIdentities.get(ownIdentity).containsKey(oldIdentity.getId())) { + continue; + } + Identity newIdentity = currentIdentities.get(ownIdentity).get(oldIdentity.getId()); + Map oldProperties = oldIdentity.getProperties(); + Map newProperties = newIdentity.getProperties(); + if (oldProperties.size() != newProperties.size()) { + identityListenerManager.fireIdentityUpdated(ownIdentity, newIdentity); + continue; + } + for (Entry oldProperty : oldProperties.entrySet()) { + if (!newProperties.containsKey(oldProperty.getKey()) || !newProperties.get(oldProperty.getKey()).equals(oldProperty.getValue())) { + identityListenerManager.fireIdentityUpdated(ownIdentity, newIdentity); + break; + } + } } } - } - /* remember the current set of identities. */ - oldIdentities = currentIdentities; + /* remember the current set of identities. */ + oldIdentities = currentIdentities; + } - } catch (PluginException pe1) { - logger.log(Level.WARNING, "WoT has disappeared!", pe1); + } catch (WebOfTrustException wote1) { + logger.log(Level.WARNING, "WoT has disappeared!", wote1); } /* wait a minute before checking again. */ diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/OwnIdentity.java b/src/main/java/net/pterodactylus/sone/freenet/wot/OwnIdentity.java index d9ec160..68a10f4 100644 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/OwnIdentity.java +++ b/src/main/java/net/pterodactylus/sone/freenet/wot/OwnIdentity.java @@ -17,45 +17,119 @@ package net.pterodactylus.sone.freenet.wot; +import java.util.Map; +import java.util.Set; + /** - * An own identity is an identity that the owner of the node has full control - * over. + * Defines a local identity, an own identity. * * @author David ‘Bombe’ Roden */ -public class OwnIdentity extends Identity { +public interface OwnIdentity extends Identity { + + /** + * Returns the insert URI of the identity. + * + * @return The insert URI of the identity + */ + public String getInsertUri(); - /** The insert URI of the identity. */ - private final String insertUri; + /** + * Adds the given context to this identity. + *

+ * This method is only called by the {@link IdentityManager}. + * + * @param context + * The context to add + * @throws WebOfTrustException + * if an error occurs + */ + public void addContext(String context) throws WebOfTrustException; /** - * Creates a new own identity. + * Sets all contexts of this identity. + *

+ * This method is only called by the {@link IdentityManager}. * - * @param id - * The ID of the identity - * @param nickname - * The nickname of the identity - * @param requestUri - * The request URI of the identity - * @param insertUri - * The insert URI of the identity + * @param contexts + * All contexts of the identity + * @throws WebOfTrustException + * if an error occurs */ - public OwnIdentity(String id, String nickname, String requestUri, String insertUri) { - super(id, nickname, requestUri); - this.insertUri = insertUri; - } + public void setContexts(Set contexts) throws WebOfTrustException; - // - // ACCESSORS - // + /** + * Removes the given context from this identity. + *

+ * This method is only called by the {@link IdentityManager}. + * + * @param context + * The context to remove + * @throws WebOfTrustException + * if an error occurs + */ + public void removeContext(String context) throws WebOfTrustException; /** - * Returns the insert URI of the identity. + * Sets the property with the given name to the given value. + *

+ * This method is only called by the {@link IdentityManager}. * - * @return The insert URI of the identity + * @param name + * The name of the property + * @param value + * The value of the property + * @throws WebOfTrustException + * if an error occurs + */ + public void setProperty(String name, String value) throws WebOfTrustException; + + /** + * Sets all properties of this identity. + *

+ * This method is only called by the {@link IdentityManager}. + * + * @param properties + * The new properties of this identity + * @throws WebOfTrustException + * if an error occurs + */ + public void setProperties(Map properties) throws WebOfTrustException; + + /** + * Removes the property with the given name. + *

+ * This method is only called by the {@link IdentityManager}. + * + * @param name + * The name of the property to remove + * @throws WebOfTrustException + * if an error occurs + */ + public void removeProperty(String name) throws WebOfTrustException; + + /** + * Sets the trust for the given target identity. + * + * @param target + * The target to set the trust for + * @param trustValue + * The new trust value (from {@code -100} or {@code 100}) + * @param comment + * The comment for the trust assignment + * @throws WebOfTrustException + * if an error occurs + */ + public void setTrust(Identity target, int trustValue, String comment) throws WebOfTrustException; + + /** + * Removes any trust assignment for the given target identity. + * + * @param target + * The targe to remove the trust assignment for + * @throws WebOfTrustException + * if an error occurs */ - public String getInsertUri() { - return insertUri; - } + public void removeTrust(Identity target) throws WebOfTrustException; } diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/PluginConnector.java b/src/main/java/net/pterodactylus/sone/freenet/wot/PluginConnector.java deleted file mode 100644 index 561f092..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/PluginConnector.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Sone - PluginConnector.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 . - */ - -package net.pterodactylus.sone.freenet.wot; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import net.pterodactylus.util.collection.Pair; -import freenet.pluginmanager.FredPluginTalker; -import freenet.pluginmanager.PluginNotFoundException; -import freenet.pluginmanager.PluginRespirator; -import freenet.pluginmanager.PluginTalker; -import freenet.support.SimpleFieldSet; -import freenet.support.api.Bucket; - -/** - * Interface for talking to other plugins. Other plugins are identified by their - * name and a unique connection identifier. - * - * @author David ‘Bombe’ Roden - */ -public class PluginConnector implements FredPluginTalker { - - /** The plugin respirator. */ - private final PluginRespirator pluginRespirator; - - /** Connector listener managers for all plugin connections. */ - private final Map, ConnectorListenerManager> connectorListenerManagers = Collections.synchronizedMap(new HashMap, ConnectorListenerManager>()); - - /** - * Creates a new plugin connector. - * - * @param pluginRespirator - * The plugin respirator - */ - public PluginConnector(PluginRespirator pluginRespirator) { - this.pluginRespirator = pluginRespirator; - } - - // - // LISTENER MANAGEMENT - // - - /** - * Adds a connection listener for the given plugin connection. - * - * @param pluginName - * The name of the plugin - * @param identifier - * The identifier of the connection - * @param connectorListener - * The listener to add - */ - public void addConnectorListener(String pluginName, String identifier, ConnectorListener connectorListener) { - getConnectorListenerManager(pluginName, identifier).addListener(connectorListener); - } - - /** - * Removes a connection listener for the given plugin connection. - * - * @param pluginName - * The name of the plugin - * @param identifier - * The identifier of the connection - * @param connectorListener - * The listener to remove - */ - public void removeConnectorListener(String pluginName, String identifier, ConnectorListener connectorListener) { - getConnectorListenerManager(pluginName, identifier).removeListener(connectorListener); - } - - // - // ACTIONS - // - - /** - * Sends a request to the given plugin. - * - * @param pluginName - * The name of the plugin - * @param identifier - * The identifier of the connection - * @param fields - * The fields of the message - * @throws PluginException - * if the plugin can not be found - */ - public void sendRequest(String pluginName, String identifier, SimpleFieldSet fields) throws PluginException { - sendRequest(pluginName, identifier, fields, null); - } - - /** - * Sends a request to the given plugin. - * - * @param pluginName - * The name of the plugin - * @param identifier - * The identifier of the connection - * @param fields - * The fields of the message - * @param data - * The payload of the message (may be null) - * @throws PluginException - * if the plugin can not be found - */ - public void sendRequest(String pluginName, String identifier, SimpleFieldSet fields, Bucket data) throws PluginException { - getPluginTalker(pluginName, identifier).send(fields, data); - } - - // - // PRIVATE METHODS - // - - /** - * Returns the connection listener manager for the given plugin connection, - * creating a new one if none does exist yet. - * - * @param pluginName - * The name of the plugin - * @param identifier - * The identifier of the connection - * @return The connection listener manager - */ - private ConnectorListenerManager getConnectorListenerManager(String pluginName, String identifier) { - return getConnectorListenerManager(pluginName, identifier, true); - } - - /** - * Returns the connection listener manager for the given plugin connection, - * optionally creating a new one if none does exist yet. - * - * @param pluginName - * The name of the plugin - * @param identifier - * The identifier of the connection - * @param create - * {@code true} to create a new manager if there is none, - * {@code false} to return {@code null} in that case - * @return The connection listener manager, or {@code null} if none existed - * and {@code create} is {@code false} - */ - private ConnectorListenerManager getConnectorListenerManager(String pluginName, String identifier, boolean create) { - ConnectorListenerManager connectorListenerManager = connectorListenerManagers.get(new Pair(pluginName, identifier)); - if (create && (connectorListenerManager == null)) { - connectorListenerManager = new ConnectorListenerManager(this); - connectorListenerManagers.put(new Pair(pluginName, identifier), connectorListenerManager); - } - return connectorListenerManager; - } - - /** - * Returns the plugin talker for the given plugin connection. - * - * @param pluginName - * The name of the plugin - * @param identifier - * The identifier of the connection - * @return The plugin talker - * @throws PluginException - * if the plugin can not be found - */ - private PluginTalker getPluginTalker(String pluginName, String identifier) throws PluginException { - try { - return pluginRespirator.getPluginTalker(this, pluginName, identifier); - } catch (PluginNotFoundException pnfe1) { - throw new PluginException(pnfe1); - } - } - - // - // INTERFACE FredPluginTalker - // - - /** - * {@inheritDoc} - */ - @Override - public void onReply(String pluginName, String identifier, SimpleFieldSet params, Bucket data) { - ConnectorListenerManager connectorListenerManager = getConnectorListenerManager(pluginName, identifier, false); - if (connectorListenerManager == null) { - /* we don’t care about events for this plugin. */ - return; - } - connectorListenerManager.fireReceivedReply(params, data); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/PluginException.java b/src/main/java/net/pterodactylus/sone/freenet/wot/PluginException.java deleted file mode 100644 index 36cf49a..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/PluginException.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Sone - PluginException.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 . - */ - -package net.pterodactylus.sone.freenet.wot; - -/** - * Exception that signals an error when communicating with a plugin. - * - * @author David ‘Bombe’ Roden - */ -public class PluginException extends Exception { - - /** - * Creates a new plugin exception. - */ - public PluginException() { - super(); - } - - /** - * Creates a new plugin exception. - * - * @param message - * The message of the exception - */ - public PluginException(String message) { - super(message); - } - - /** - * Creates a new plugin exception. - * - * @param cause - * The cause of the exception - */ - public PluginException(Throwable cause) { - super(cause); - } - - /** - * Creates a new plugin exception. - * - * @param message - * The message of the exception - * @param cause - * The cause of the exception - */ - public PluginException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/Trust.java b/src/main/java/net/pterodactylus/sone/freenet/wot/Trust.java new file mode 100644 index 0000000..975dd3f --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/freenet/wot/Trust.java @@ -0,0 +1,82 @@ +/* + * Sone - Trust.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 . + */ + +package net.pterodactylus.sone.freenet.wot; + +/** + * Container class for trust in the web of trust. + * + * @author David ‘Bombe’ Roden + */ +public class Trust { + + /** Explicitely assigned trust. */ + private final Integer explicit; + + /** Implicitely calculated trust. */ + private final Integer implicit; + + /** The distance from the owner of the trust tree. */ + private final Integer distance; + + /** + * Creates a new trust container. + * + * @param explicit + * The explicit trust + * @param implicit + * The implicit trust + * @param distance + * The distance + */ + public Trust(Integer explicit, Integer implicit, Integer distance) { + this.explicit = explicit; + this.implicit = implicit; + this.distance = distance; + } + + /** + * Returns the trust explicitely assigned to an identity. + * + * @return The explicitely assigned trust, or {@code null} if the identity + * is not in the own identity’s trust tree + */ + public Integer getExplicit() { + return explicit; + } + + /** + * Returns the implicitely assigned trust, or the calculated trust. + * + * @return The calculated trust, or {@code null} if the identity is not in + * the own identity’s trust tree + */ + public Integer getImplicit() { + return implicit; + } + + /** + * Returns the distance of the trusted identity from the trusting identity. + * + * @return The distance from the own identity, or {@code null} if the + * identity is not in the own identity’s trust tree + */ + public Integer getDistance() { + return distance; + } + +} diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustConnector.java b/src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustConnector.java index becb178..b6d68d3 100644 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustConnector.java +++ b/src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustConnector.java @@ -17,7 +17,6 @@ package net.pterodactylus.sone.freenet.wot; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -25,6 +24,9 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import net.pterodactylus.sone.freenet.plugin.ConnectorListener; +import net.pterodactylus.sone.freenet.plugin.PluginConnector; +import net.pterodactylus.sone.freenet.plugin.PluginException; import net.pterodactylus.util.logging.Logging; import freenet.support.SimpleFieldSet; import freenet.support.api.Bucket; @@ -45,8 +47,8 @@ public class WebOfTrustConnector implements ConnectorListener { /** A random connection identifier. */ private static final String PLUGIN_CONNECTION_IDENTIFIER = "Sone-WoT-Connector-" + Math.abs(Math.random()); - /** The current replies that we wait for. */ - private final Map replies = Collections.synchronizedMap(new HashMap()); + /** The current reply. */ + private Reply reply; /** The plugin connector. */ private final PluginConnector pluginConnector; @@ -71,11 +73,11 @@ public class WebOfTrustConnector implements ConnectorListener { * Loads all own identities from the Web of Trust plugin. * * @return All own identity - * @throws PluginException + * @throws WebOfTrustException * if the own identities can not be loaded */ - public Set loadAllOwnIdentities() throws PluginException { - Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetOwnIdentities").get(), "OwnIdentities"); + public Set loadAllOwnIdentities() throws WebOfTrustException { + Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetOwnIdentities").get()); SimpleFieldSet fields = reply.getFields(); int ownIdentityCounter = -1; Set ownIdentities = new HashSet(); @@ -87,9 +89,9 @@ public class WebOfTrustConnector implements ConnectorListener { String requestUri = fields.get("RequestURI" + ownIdentityCounter); String insertUri = fields.get("InsertURI" + ownIdentityCounter); String nickname = fields.get("Nickname" + ownIdentityCounter); - OwnIdentity ownIdentity = new OwnIdentity(id, nickname, requestUri, insertUri); - ownIdentity.setContexts(parseContexts("Contexts" + ownIdentityCounter + ".", fields)); - ownIdentity.setProperties(parseProperties("Properties" + ownIdentityCounter + ".", fields)); + DefaultOwnIdentity ownIdentity = new DefaultOwnIdentity(this, id, nickname, requestUri, insertUri); + ownIdentity.setContextsPrivate(parseContexts("Contexts" + ownIdentityCounter + ".", fields)); + ownIdentity.setPropertiesPrivate(parseProperties("Properties" + ownIdentityCounter + ".", fields)); ownIdentities.add(ownIdentity); } return ownIdentities; @@ -122,7 +124,7 @@ public class WebOfTrustConnector implements ConnectorListener { * if an error occured talking to the Web of Trust plugin */ public Set loadTrustedIdentities(OwnIdentity ownIdentity, String context) throws PluginException { - Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetIdentitiesByScore").put("TreeOwner", ownIdentity.getId()).put("Selection", "+").put("Context", (context == null) ? "" : context).get(), "Identities"); + Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetIdentitiesByScore").put("TreeOwner", ownIdentity.getId()).put("Selection", "+").put("Context", (context == null) ? "" : context).get()); SimpleFieldSet fields = reply.getFields(); Set identities = new HashSet(); int identityCounter = -1; @@ -133,9 +135,9 @@ public class WebOfTrustConnector implements ConnectorListener { } String nickname = fields.get("Nickname" + identityCounter); String requestUri = fields.get("RequestURI" + identityCounter); - Identity identity = new Identity(id, nickname, requestUri); - identity.setContexts(parseContexts("Contexts" + identityCounter + ".", fields)); - identity.setProperties(parseProperties("Properties" + identityCounter + ".", fields)); + DefaultIdentity identity = new DefaultIdentity(this, id, nickname, requestUri); + identity.setContextsPrivate(parseContexts("Contexts" + identityCounter + ".", fields)); + identity.setPropertiesPrivate(parseProperties("Properties" + identityCounter + ".", fields)); identities.add(identity); } return identities; @@ -152,7 +154,7 @@ public class WebOfTrustConnector implements ConnectorListener { * if an error occured talking to the Web of Trust plugin */ public void addContext(OwnIdentity ownIdentity, String context) throws PluginException { - performRequest(SimpleFieldSetConstructor.create().put("Message", "AddContext").put("Identity", ownIdentity.getId()).put("Context", context).get(), "ContextAdded"); + performRequest(SimpleFieldSetConstructor.create().put("Message", "AddContext").put("Identity", ownIdentity.getId()).put("Context", context).get()); } /** @@ -166,7 +168,7 @@ public class WebOfTrustConnector implements ConnectorListener { * if an error occured talking to the Web of Trust plugin */ public void removeContext(OwnIdentity ownIdentity, String context) throws PluginException { - performRequest(SimpleFieldSetConstructor.create().put("Message", "RemoveContext").put("Identity", ownIdentity.getId()).put("Context", context).get(), "ContextRemoved"); + performRequest(SimpleFieldSetConstructor.create().put("Message", "RemoveContext").put("Identity", ownIdentity.getId()).put("Context", context).get()); } /** @@ -181,7 +183,7 @@ public class WebOfTrustConnector implements ConnectorListener { * if an error occured talking to the Web of Trust plugin */ public String getProperty(Identity identity, String name) throws PluginException { - Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetProperty").put("Identity", identity.getId()).put("Property", name).get(), "PropertyValue"); + Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetProperty").put("Identity", identity.getId()).put("Property", name).get()); return reply.getFields().get("Property"); } @@ -198,7 +200,7 @@ public class WebOfTrustConnector implements ConnectorListener { * if an error occured talking to the Web of Trust plugin */ public void setProperty(OwnIdentity ownIdentity, String name, String value) throws PluginException { - performRequest(SimpleFieldSetConstructor.create().put("Message", "SetProperty").put("Identity", ownIdentity.getId()).put("Property", name).put("Value", value).get(), "PropertyAdded"); + performRequest(SimpleFieldSetConstructor.create().put("Message", "SetProperty").put("Identity", ownIdentity.getId()).put("Property", name).put("Value", value).get()); } /** @@ -212,7 +214,74 @@ public class WebOfTrustConnector implements ConnectorListener { * if an error occured talking to the Web of Trust plugin */ public void removeProperty(OwnIdentity ownIdentity, String name) throws PluginException { - performRequest(SimpleFieldSetConstructor.create().put("Message", "RemoveProperty").put("Identity", ownIdentity.getId()).put("Property", name).get(), "PropertyRemoved"); + performRequest(SimpleFieldSetConstructor.create().put("Message", "RemoveProperty").put("Identity", ownIdentity.getId()).put("Property", name).get()); + } + + /** + * Returns the trust for the given identity assigned to it by the given own + * identity. + * + * @param ownIdentity + * The own identity + * @param identity + * The identity to get the trust for + * @return The trust for the given identity + * @throws PluginException + * if an error occured talking to the Web of Trust plugin + */ + public Trust getTrust(OwnIdentity ownIdentity, Identity identity) throws PluginException { + Reply getTrustReply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetIdentity").put("TreeOwner", ownIdentity.getId()).put("Identity", identity.getId()).get()); + String trust = getTrustReply.getFields().get("Trust"); + String score = getTrustReply.getFields().get("Score"); + String rank = getTrustReply.getFields().get("Rank"); + Integer explicit = null; + Integer implicit = null; + Integer distance = null; + try { + explicit = Integer.valueOf(trust); + } catch (NumberFormatException nfe1) { + /* ignore. */ + } + try { + implicit = Integer.valueOf(score); + distance = Integer.valueOf(rank); + } catch (NumberFormatException nfe1) { + /* ignore. */ + } + return new Trust(explicit, implicit, distance); + } + + /** + * Sets the trust for the given identity. + * + * @param ownIdentity + * The trusting identity + * @param identity + * The trusted identity + * @param trust + * The amount of trust (-100 thru 100) + * @param comment + * The comment or explanation of the trust value + * @throws PluginException + * if an error occured talking to the Web of Trust plugin + */ + public void setTrust(OwnIdentity ownIdentity, Identity identity, int trust, String comment) throws PluginException { + performRequest(SimpleFieldSetConstructor.create().put("Message", "SetTrust").put("Truster", ownIdentity.getId()).put("Trustee", identity.getId()).put("Value", String.valueOf(trust)).put("Comment", comment).get()); + } + + /** + * Removes any trust assignment of the given own identity for the given + * identity. + * + * @param ownIdentity + * The own identity + * @param identity + * The identity to remove all trust for + * @throws WebOfTrustException + * if an error occurs + */ + public void removeTrust(OwnIdentity ownIdentity, Identity identity) throws WebOfTrustException { + performRequest(SimpleFieldSetConstructor.create().put("Message", "RemoveTrust").put("Truster", ownIdentity.getId()).put("Trustee", identity.getId()).get()); } /** @@ -223,7 +292,7 @@ public class WebOfTrustConnector implements ConnectorListener { * if the plugin is not loaded */ public void ping() throws PluginException { - performRequest(SimpleFieldSetConstructor.create().put("Message", "Ping").get(), "Pong"); + performRequest(SimpleFieldSetConstructor.create().put("Message", "Ping").get()); } // @@ -281,14 +350,12 @@ public class WebOfTrustConnector implements ConnectorListener { * * @param fields * The fields of the message - * @param targetMessages - * The messages of the reply to wait for * @return The reply message * @throws PluginException * if the request could not be sent */ - private Reply performRequest(SimpleFieldSet fields, String... targetMessages) throws PluginException { - return performRequest(fields, null, targetMessages); + private Reply performRequest(SimpleFieldSet fields) throws PluginException { + return performRequest(fields, null); } /** @@ -299,43 +366,24 @@ public class WebOfTrustConnector implements ConnectorListener { * The fields of the message * @param data * The payload of the message - * @param targetMessages - * The messages of the reply to wait for * @return The reply message * @throws PluginException * if the request could not be sent */ - private Reply performRequest(SimpleFieldSet fields, Bucket data, String... targetMessages) throws PluginException { - @SuppressWarnings("synthetic-access") - Reply reply = new Reply(); - for (String targetMessage : targetMessages) { - replies.put(targetMessage, reply); - } - replies.put("Error", reply); + private synchronized Reply performRequest(SimpleFieldSet fields, Bucket data) throws PluginException { + reply = new Reply(); + logger.log(Level.FINE, "Sending FCP Request: " + fields.get("Message")); synchronized (reply) { pluginConnector.sendRequest(WOT_PLUGIN_NAME, PLUGIN_CONNECTION_IDENTIFIER, fields, data); try { - long now = System.currentTimeMillis(); - while ((reply.getFields() == null) && ((System.currentTimeMillis() - now) < 60000)) { - reply.wait(60000 - (System.currentTimeMillis() - now)); - } - if (reply.getFields() == null) { - for (String targetMessage : targetMessages) { - replies.remove(targetMessage); - } - replies.remove("Error"); - throw new PluginException("Timeout waiting for " + targetMessages[0] + "!"); - } + reply.wait(); } catch (InterruptedException ie1) { - logger.log(Level.WARNING, "Got interrupted while waiting for reply on " + targetMessages[0] + ".", ie1); + logger.log(Level.WARNING, "Got interrupted while waiting for reply on " + fields.get("Message") + ".", ie1); } } - for (String targetMessage : targetMessages) { - replies.remove(targetMessage); - } - replies.remove("Error"); - if ((reply.getFields() != null) && reply.getFields().get("Message").equals("Error")) { - throw new PluginException("Could not perform request for " + targetMessages[0]); + logger.log(Level.FINEST, "Received FCP Response for %s: %s", new Object[] { fields.get("Message"), (reply.getFields() != null) ? reply.getFields().get("Message") : null }); + if ((reply.getFields() == null) || "Error".equals(reply.getFields().get("Message"))) { + throw new PluginException("Could not perform request for " + fields.get("Message")); } return reply; } @@ -351,11 +399,6 @@ public class WebOfTrustConnector implements ConnectorListener { public void receivedReply(PluginConnector pluginConnector, SimpleFieldSet fields, Bucket data) { String messageName = fields.get("Message"); logger.log(Level.FINEST, "Received Reply from Plugin: " + messageName); - Reply reply = replies.remove(messageName); - if (reply == null) { - logger.log(Level.FINE, "Not waiting for a “%s” message.", messageName); - return; - } synchronized (reply) { reply.setFields(fields); reply.setData(data); @@ -376,6 +419,11 @@ public class WebOfTrustConnector implements ConnectorListener { /** The payload of the reply. */ private Bucket data; + /** Empty constructor. */ + public Reply() { + /* do nothing. */ + } + /** * Returns the fields of the reply. * diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustException.java b/src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustException.java new file mode 100644 index 0000000..f59b2a3 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustException.java @@ -0,0 +1,67 @@ +/* + * Sone - WebOfTrustException.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 . + */ + +package net.pterodactylus.sone.freenet.wot; + +/** + * Exception that signals an error processing web of trust identities, mostly + * when communicating with the web of trust plugin. + * + * @author David ‘Bombe’ Roden + */ +public class WebOfTrustException extends Exception { + + /** + * Creates a new web of trust exception. + */ + public WebOfTrustException() { + super(); + } + + /** + * Creates a new web of trust exception. + * + * @param message + * The message of the exception + */ + public WebOfTrustException(String message) { + super(message); + } + + /** + * Creates a new web of trust exception. + * + * @param cause + * The cause of the exception + */ + public WebOfTrustException(Throwable cause) { + super(cause); + } + + /** + * Creates a new web of trust exception. + * + * @param message + * The message of the exception + * @param cause + * The cause of the exception + */ + public WebOfTrustException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/main/SonePlugin.java b/src/main/java/net/pterodactylus/sone/main/SonePlugin.java index a0a22f9..4fc34d9 100644 --- a/src/main/java/net/pterodactylus/sone/main/SonePlugin.java +++ b/src/main/java/net/pterodactylus/sone/main/SonePlugin.java @@ -25,8 +25,8 @@ import java.util.logging.Logger; import net.pterodactylus.sone.core.Core; import net.pterodactylus.sone.core.FreenetInterface; import net.pterodactylus.sone.freenet.PluginStoreConfigurationBackend; +import net.pterodactylus.sone.freenet.plugin.PluginConnector; import net.pterodactylus.sone.freenet.wot.IdentityManager; -import net.pterodactylus.sone.freenet.wot.PluginConnector; import net.pterodactylus.sone.freenet.wot.WebOfTrustConnector; import net.pterodactylus.sone.web.WebInterface; import net.pterodactylus.util.config.Configuration; @@ -78,7 +78,7 @@ public class SonePlugin implements FredPlugin, FredPluginL10n, FredPluginBaseL10 } /** The version. */ - public static final Version VERSION = new Version(0, 3, 7); + public static final Version VERSION = new Version(0, 4); /** The logger. */ private static final Logger logger = Logging.getLogger(SonePlugin.class); diff --git a/src/main/java/net/pterodactylus/sone/template/JavascriptFilter.java b/src/main/java/net/pterodactylus/sone/template/JavascriptFilter.java new file mode 100644 index 0000000..0cd55de --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/template/JavascriptFilter.java @@ -0,0 +1,68 @@ +/* + * Sone - JavascriptFilter.java - Copyright © 2011 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 . + */ + +package net.pterodactylus.sone.template; + +import java.util.Map; + +import net.pterodactylus.util.number.Hex; +import net.pterodactylus.util.template.DataProvider; +import net.pterodactylus.util.template.Filter; + +/** + * Escapes double quotes, backslashes, carriage returns and line feeds, and + * additionally encloses a given string with double quotes to make it possible + * to use a string in Javascript. + * + * @author David ‘Bombe’ Roden + */ +public class JavascriptFilter implements Filter { + + /** + * {@inheritDoc} + */ + @Override + public Object format(DataProvider dataProvider, Object data, Map parameters) { + StringBuilder javascriptString = new StringBuilder(); + javascriptString.append('"'); + for (char c : String.valueOf(data).toCharArray()) { + if (c == '\r') { + javascriptString.append("\\r"); + continue; + } + if (c == '\n') { + javascriptString.append("\\n"); + continue; + } + if (c == '\t') { + javascriptString.append("\\t"); + continue; + } + if ((c == '"') || (c == '\\')) { + javascriptString.append('\\'); + javascriptString.append(c); + } else if (c < 32) { + javascriptString.append("\\x").append(Hex.toHex((byte) c)); + } else { + javascriptString.append(c); + } + } + javascriptString.append('"'); + return javascriptString.toString(); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/template/SoneAccessor.java b/src/main/java/net/pterodactylus/sone/template/SoneAccessor.java index c9c5f48..0cb92ff 100644 --- a/src/main/java/net/pterodactylus/sone/template/SoneAccessor.java +++ b/src/main/java/net/pterodactylus/sone/template/SoneAccessor.java @@ -21,6 +21,7 @@ import net.pterodactylus.sone.core.Core; import net.pterodactylus.sone.core.Core.SoneStatus; import net.pterodactylus.sone.data.Profile; import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.freenet.wot.Trust; import net.pterodactylus.util.template.Accessor; import net.pterodactylus.util.template.DataProvider; import net.pterodactylus.util.template.ReflectionAccessor; @@ -89,6 +90,12 @@ public class SoneAccessor extends ReflectionAccessor { return core.isNewSone(sone); } else if (member.equals("locked")) { return core.isLocked(sone); + } else if (member.equals("trust")) { + Sone currentSone = (Sone) dataProvider.getData("currentSone"); + Trust trust = core.getTrust(currentSone, sone); + if (trust == null) { + return new Trust(null, null, null); + } } return super.get(dataProvider, object, member); } diff --git a/src/main/java/net/pterodactylus/sone/template/TrustAccessor.java b/src/main/java/net/pterodactylus/sone/template/TrustAccessor.java new file mode 100644 index 0000000..4dfd81e --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/template/TrustAccessor.java @@ -0,0 +1,54 @@ +/* + * Sone - TrustAccessor.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 . + */ + +package net.pterodactylus.sone.template; + +import net.pterodactylus.sone.freenet.wot.Trust; +import net.pterodactylus.util.template.Accessor; +import net.pterodactylus.util.template.DataProvider; +import net.pterodactylus.util.template.ReflectionAccessor; + +/** + * {@link Accessor} implementation for {@link Trust} values, adding the + * following properties: + *

+ *
assigned
+ *
{@link Boolean} that indicates whether this trust relationship has an + * explicit value assigned to it.
+ *
+ * + * @author David ‘Bombe’ Roden + */ +public class TrustAccessor extends ReflectionAccessor { + + /** + * {@inheritDoc} + */ + @Override + public Object get(DataProvider dataProvider, Object object, String member) { + Trust trust = (Trust) object; + if ("assigned".equals(member)) { + return trust.getExplicit() != null; + } else if ("maximum".equals(member)) { + return ((trust.getExplicit() != null) && (trust.getExplicit() >= 100)) || ((trust.getImplicit() != null) && (trust.getImplicit() >= 100)); + } else if ("hasDistance".equals(member)) { + return (trust.getDistance() != null) && (trust.getDistance() != Integer.MAX_VALUE); + } + return super.get(dataProvider, object, member); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/CreateSonePage.java b/src/main/java/net/pterodactylus/sone/web/CreateSonePage.java index 04b5d37..46302b0 100644 --- a/src/main/java/net/pterodactylus/sone/web/CreateSonePage.java +++ b/src/main/java/net/pterodactylus/sone/web/CreateSonePage.java @@ -112,7 +112,6 @@ public class CreateSonePage extends SoneTemplatePage { return; } /* create Sone. */ - webInterface.getCore().getIdentityManager().addContext(selectedIdentity, "Sone"); Sone sone = webInterface.getCore().createSone(selectedIdentity); if (sone == null) { logger.log(Level.SEVERE, "Could not create Sone for OwnIdentity: %s", selectedIdentity); diff --git a/src/main/java/net/pterodactylus/sone/web/DeleteProfileFieldPage.java b/src/main/java/net/pterodactylus/sone/web/DeleteProfileFieldPage.java new file mode 100644 index 0000000..9b52b04 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/DeleteProfileFieldPage.java @@ -0,0 +1,84 @@ +/* + * Sone - DeleteProfileFieldPage.java - Copyright © 2011 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 . + */ + +package net.pterodactylus.sone.web; + +import net.pterodactylus.sone.data.Profile; +import net.pterodactylus.sone.data.Profile.Field; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.web.page.Page.Request.Method; +import net.pterodactylus.util.template.DataProvider; +import net.pterodactylus.util.template.Template; + +/** + * Page that lets the user confirm the deletion of a profile field. + * + * @author David ‘Bombe’ Roden + */ +public class DeleteProfileFieldPage extends SoneTemplatePage { + + /** + * Creates a new “delete profile field” page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public DeleteProfileFieldPage(Template template, WebInterface webInterface) { + super("deleteProfileField.html", template, "Page.DeleteProfileField.Title", webInterface, true); + } + + // + // SONETEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException { + super.processTemplate(request, dataProvider); + Sone currentSone = getCurrentSone(request.getToadletContext()); + Profile profile = currentSone.getProfile(); + + /* get parameters from request. */ + String fieldId = request.getHttpRequest().getParam("field"); + Field field = profile.getFieldById(fieldId); + if (field == null) { + throw new RedirectException("invalid.html"); + } + + /* process POST request. */ + if (request.getMethod() == Method.POST) { + if (request.getHttpRequest().getPartAsStringFailsafe("confirm", 4).equals("true")) { + fieldId = request.getHttpRequest().getParam("field"); + field = profile.getFieldById(fieldId); + if (field == null) { + throw new RedirectException("invalid.html"); + } + profile.removeField(field); + currentSone.setProfile(profile); + } + throw new RedirectException("editProfile.html#profile-fields"); + } + + /* set current values in template. */ + dataProvider.set("field", field); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/DistrustPage.java b/src/main/java/net/pterodactylus/sone/web/DistrustPage.java new file mode 100644 index 0000000..9cbbb55 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/DistrustPage.java @@ -0,0 +1,69 @@ +/* + * Sone - TrustPage.java - Copyright © 2011 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 . + */ + +package net.pterodactylus.sone.web; + +import net.pterodactylus.sone.core.Core; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.web.page.Page.Request.Method; +import net.pterodactylus.util.template.DataProvider; +import net.pterodactylus.util.template.Template; + +/** + * Page that lets the user distrust another Sone. This will assign a + * configurable (negative) amount of trust to an identity. + * + * @see Core#distrustSone(Sone, Sone) + * @author David ‘Bombe’ Roden + */ +public class DistrustPage extends SoneTemplatePage { + + /** + * Creates a new “distrust Sone” page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public DistrustPage(Template template, WebInterface webInterface) { + super("distrust.html", template, "Page.Distrust.Title", webInterface, true); + } + + // + // SONETEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException { + super.processTemplate(request, dataProvider); + if (request.getMethod() == Method.POST) { + String returnPath = request.getHttpRequest().getPartAsStringFailsafe("returnPath", 256); + String identity = request.getHttpRequest().getPartAsStringFailsafe("sone", 44); + Sone currentSone = getCurrentSone(request.getToadletContext()); + Sone sone = webInterface.getCore().getSone(identity, false); + if (sone != null) { + webInterface.getCore().distrustSone(currentSone, sone); + } + throw new RedirectException(returnPath); + } + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/EditProfileFieldPage.java b/src/main/java/net/pterodactylus/sone/web/EditProfileFieldPage.java new file mode 100644 index 0000000..1b64b08 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/EditProfileFieldPage.java @@ -0,0 +1,90 @@ +/* + * Sone - EditProfileFieldPage.java - Copyright © 2011 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 . + */ + +package net.pterodactylus.sone.web; + +import net.pterodactylus.sone.data.Profile; +import net.pterodactylus.sone.data.Profile.Field; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.web.page.Page.Request.Method; +import net.pterodactylus.util.template.DataProvider; +import net.pterodactylus.util.template.Template; + +/** + * Page that lets the user edit the name of a profile field. + * + * @author David ‘Bombe’ Roden + */ +public class EditProfileFieldPage extends SoneTemplatePage { + + /** + * Creates a new “edit profile field” page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public EditProfileFieldPage(Template template, WebInterface webInterface) { + super("editProfileField.html", template, "Page.EditProfileField.Title", webInterface, true); + } + + // + // SONETEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException { + super.processTemplate(request, dataProvider); + Sone currentSone = getCurrentSone(request.getToadletContext()); + Profile profile = currentSone.getProfile(); + + /* get parameters from request. */ + String fieldId = request.getHttpRequest().getParam("field"); + Field field = profile.getFieldById(fieldId); + if (field == null) { + throw new RedirectException("invalid.html"); + } + + /* process the POST request. */ + if (request.getMethod() == Method.POST) { + if (request.getHttpRequest().getPartAsStringFailsafe("cancel", 4).equals("true")) { + throw new RedirectException("editProfile.html#profile-fields"); + } + fieldId = request.getHttpRequest().getPartAsStringFailsafe("field", 36); + field = profile.getFieldById(fieldId); + if (field == null) { + throw new RedirectException("invalid.html"); + } + String name = request.getHttpRequest().getPartAsStringFailsafe("name", 256); + Field existingField = profile.getFieldByName(name); + if ((existingField == null) || (existingField.equals(field))) { + field.setName(name); + currentSone.setProfile(profile); + throw new RedirectException("editProfile.html#profile-fields"); + } + dataProvider.set("duplicateFieldName", true); + } + + /* store current values in template. */ + dataProvider.set("field", field); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/EditProfilePage.java b/src/main/java/net/pterodactylus/sone/web/EditProfilePage.java index 01d4815..380e054 100644 --- a/src/main/java/net/pterodactylus/sone/web/EditProfilePage.java +++ b/src/main/java/net/pterodactylus/sone/web/EditProfilePage.java @@ -17,7 +17,10 @@ package net.pterodactylus.sone.web; +import java.util.List; + import net.pterodactylus.sone.data.Profile; +import net.pterodactylus.sone.data.Profile.Field; import net.pterodactylus.sone.data.Sone; import net.pterodactylus.sone.web.page.Page.Request.Method; import net.pterodactylus.util.number.Numbers; @@ -63,21 +66,68 @@ public class EditProfilePage extends SoneTemplatePage { Integer birthDay = profile.getBirthDay(); Integer birthMonth = profile.getBirthMonth(); Integer birthYear = profile.getBirthYear(); + List fields = profile.getFields(); if (request.getMethod() == Method.POST) { - firstName = request.getHttpRequest().getPartAsStringFailsafe("first-name", 256).trim(); - middleName = request.getHttpRequest().getPartAsStringFailsafe("middle-name", 256).trim(); - lastName = request.getHttpRequest().getPartAsStringFailsafe("last-name", 256).trim(); - birthDay = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("birth-day", 256).trim()); - birthMonth = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("birth-month", 256).trim()); - birthYear = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("birth-year", 256).trim()); - profile.setFirstName(firstName.length() > 0 ? firstName : null); - profile.setMiddleName(middleName.length() > 0 ? middleName : null); - profile.setLastName(lastName.length() > 0 ? lastName : null); - profile.setBirthDay(birthDay).setBirthMonth(birthMonth).setBirthYear(birthYear); - if (profile.isModified()) { + if (request.getHttpRequest().getPartAsStringFailsafe("save-profile", 4).equals("true")) { + firstName = request.getHttpRequest().getPartAsStringFailsafe("first-name", 256).trim(); + middleName = request.getHttpRequest().getPartAsStringFailsafe("middle-name", 256).trim(); + lastName = request.getHttpRequest().getPartAsStringFailsafe("last-name", 256).trim(); + birthDay = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("birth-day", 256).trim()); + birthMonth = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("birth-month", 256).trim()); + birthYear = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("birth-year", 256).trim()); + profile.setFirstName(firstName.length() > 0 ? firstName : null); + profile.setMiddleName(middleName.length() > 0 ? middleName : null); + profile.setLastName(lastName.length() > 0 ? lastName : null); + profile.setBirthDay(birthDay).setBirthMonth(birthMonth).setBirthYear(birthYear); + for (Field field : fields) { + String value = request.getHttpRequest().getPartAsStringFailsafe("field-" + field.getId(), 400); + field.setValue(value); + } currentSone.setProfile(profile); + webInterface.getCore().saveSone(currentSone); + throw new RedirectException("editProfile.html"); + } else if (request.getHttpRequest().getPartAsStringFailsafe("add-field", 4).equals("true")) { + String fieldName = request.getHttpRequest().getPartAsStringFailsafe("field-name", 256).trim(); + try { + profile.addField(fieldName); + currentSone.setProfile(profile); + fields = profile.getFields(); + webInterface.getCore().saveSone(currentSone); + throw new RedirectException("editProfile.html#profile-fields"); + } catch (IllegalArgumentException iae1) { + dataProvider.set("fieldName", fieldName); + dataProvider.set("duplicateFieldName", true); + } + } else { + String id = getFieldId(request, "delete-field-"); + if (id != null) { + throw new RedirectException("deleteProfileField.html?field=" + id); + } + id = getFieldId(request, "move-up-field-"); + if (id != null) { + Field field = profile.getFieldById(id); + if (field == null) { + throw new RedirectException("invalid.html"); + } + profile.moveFieldUp(field); + currentSone.setProfile(profile); + throw new RedirectException("editProfile.html#profile-fields"); + } + id = getFieldId(request, "move-down-field-"); + if (id != null) { + Field field = profile.getFieldById(id); + if (field == null) { + throw new RedirectException("invalid.html"); + } + profile.moveFieldDown(field); + currentSone.setProfile(profile); + throw new RedirectException("editProfile.html#profile-fields"); + } + id = getFieldId(request, "edit-field-"); + if (id != null) { + throw new RedirectException("editProfileField.html?field=" + id); + } } - throw new RedirectException("index.html"); } dataProvider.set("firstName", firstName); dataProvider.set("middleName", middleName); @@ -85,6 +135,30 @@ public class EditProfilePage extends SoneTemplatePage { dataProvider.set("birthDay", birthDay); dataProvider.set("birthMonth", birthMonth); dataProvider.set("birthYear", birthYear); + dataProvider.set("fields", fields); } + // + // PRIVATE METHODS + // + + /** + * Searches for a part whose names starts with the given {@code String} and + * extracts the ID from the located name. + * + * @param request + * The request to get the parts from + * @param partNameStart + * The start of the name of the requested part + * @return The parsed ID, or {@code null} if there was no part matching the + * given string + */ + private String getFieldId(Request request, String partNameStart) { + for (String partName : request.getHttpRequest().getParts()) { + if (partName.startsWith(partNameStart)) { + return partName.substring(partNameStart.length()); + } + } + return null; + } } diff --git a/src/main/java/net/pterodactylus/sone/web/OptionsPage.java b/src/main/java/net/pterodactylus/sone/web/OptionsPage.java index 63ed6a7..0739ae7 100644 --- a/src/main/java/net/pterodactylus/sone/web/OptionsPage.java +++ b/src/main/java/net/pterodactylus/sone/web/OptionsPage.java @@ -56,6 +56,15 @@ public class OptionsPage extends SoneTemplatePage { if (request.getMethod() == Method.POST) { Integer insertionDelay = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("insertion-delay", 16)); options.getIntegerOption("InsertionDelay").set(insertionDelay); + Integer positiveTrust = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("positive-trust", 3), options.getIntegerOption("PositiveTrust").getReal()); + options.getIntegerOption("PositiveTrust").set(positiveTrust); + Integer negativeTrust = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("negative-trust", 3), options.getIntegerOption("NegativeTrust").getReal()); + options.getIntegerOption("NegativeTrust").set(negativeTrust); + String trustComment = request.getHttpRequest().getPartAsStringFailsafe("trust-comment", 256); + if (trustComment.trim().length() == 0) { + trustComment = null; + } + options.getStringOption("TrustComment").set(trustComment); boolean soneRescueMode = Boolean.parseBoolean(request.getHttpRequest().getPartAsStringFailsafe("sone-rescue-mode", 5)); options.getBooleanOption("SoneRescueMode").set(soneRescueMode); boolean clearOnNextRestart = Boolean.parseBoolean(request.getHttpRequest().getPartAsStringFailsafe("clear-on-next-restart", 5)); @@ -66,6 +75,9 @@ public class OptionsPage extends SoneTemplatePage { throw new RedirectException(getPath()); } dataProvider.set("insertion-delay", options.getIntegerOption("InsertionDelay").get()); + dataProvider.set("positive-trust", options.getIntegerOption("PositiveTrust").get()); + dataProvider.set("negative-trust", options.getIntegerOption("NegativeTrust").get()); + dataProvider.set("trust-comment", options.getStringOption("TrustComment").get()); dataProvider.set("sone-rescue-mode", options.getBooleanOption("SoneRescueMode").get()); dataProvider.set("clear-on-next-restart", options.getBooleanOption("ClearOnNextRestart").get()); dataProvider.set("really-clear-on-next-restart", options.getBooleanOption("ReallyClearOnNextRestart").get()); diff --git a/src/main/java/net/pterodactylus/sone/web/TrustPage.java b/src/main/java/net/pterodactylus/sone/web/TrustPage.java new file mode 100644 index 0000000..85341ad --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/TrustPage.java @@ -0,0 +1,69 @@ +/* + * Sone - TrustPage.java - Copyright © 2011 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 . + */ + +package net.pterodactylus.sone.web; + +import net.pterodactylus.sone.core.Core; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.web.page.Page.Request.Method; +import net.pterodactylus.util.template.DataProvider; +import net.pterodactylus.util.template.Template; + +/** + * Page that lets the user trust another Sone. This will assign a configurable + * amount of trust to an identity. + * + * @see Core#trustSone(Sone, Sone) + * @author David ‘Bombe’ Roden + */ +public class TrustPage extends SoneTemplatePage { + + /** + * Creates a new “trust Sone” page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public TrustPage(Template template, WebInterface webInterface) { + super("trust.html", template, "Page.Trust.Title", webInterface, true); + } + + // + // SONETEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException { + super.processTemplate(request, dataProvider); + if (request.getMethod() == Method.POST) { + String returnPath = request.getHttpRequest().getPartAsStringFailsafe("returnPath", 256); + String identity = request.getHttpRequest().getPartAsStringFailsafe("sone", 44); + Sone currentSone = getCurrentSone(request.getToadletContext()); + Sone sone = webInterface.getCore().getSone(identity, false); + if (sone != null) { + webInterface.getCore().trustSone(currentSone, sone); + } + throw new RedirectException(returnPath); + } + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/UntrustPage.java b/src/main/java/net/pterodactylus/sone/web/UntrustPage.java new file mode 100644 index 0000000..cc9e26d --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/UntrustPage.java @@ -0,0 +1,69 @@ +/* + * Sone - TrustPage.java - Copyright © 2011 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 . + */ + +package net.pterodactylus.sone.web; + +import net.pterodactylus.sone.core.Core; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.web.page.Page.Request.Method; +import net.pterodactylus.util.template.DataProvider; +import net.pterodactylus.util.template.Template; + +/** + * Page that lets the user untrust another Sone. This will remove all trust + * assignments for an identity. + * + * @see Core#untrustSone(Sone, Sone) + * @author David ‘Bombe’ Roden + */ +public class UntrustPage extends SoneTemplatePage { + + /** + * Creates a new “untrust Sone” page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public UntrustPage(Template template, WebInterface webInterface) { + super("untrust.html", template, "Page.Untrust.Title", webInterface, true); + } + + // + // SONETEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException { + super.processTemplate(request, dataProvider); + if (request.getMethod() == Method.POST) { + String returnPath = request.getHttpRequest().getPartAsStringFailsafe("returnPath", 256); + String identity = request.getHttpRequest().getPartAsStringFailsafe("sone", 44); + Sone currentSone = getCurrentSone(request.getToadletContext()); + Sone sone = webInterface.getCore().getSone(identity, false); + if (sone != null) { + webInterface.getCore().untrustSone(currentSone, sone); + } + throw new RedirectException(returnPath); + } + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/ViewSonePage.java b/src/main/java/net/pterodactylus/sone/web/ViewSonePage.java index 9fd6fae..9746afd 100644 --- a/src/main/java/net/pterodactylus/sone/web/ViewSonePage.java +++ b/src/main/java/net/pterodactylus/sone/web/ViewSonePage.java @@ -65,6 +65,9 @@ public class ViewSonePage extends SoneTemplatePage { @Override protected void postProcess(Request request, DataProvider dataProvider) { Sone sone = (Sone) dataProvider.get("sone"); + if (sone == null) { + return; + } List posts = sone.getPosts(); for (Post post : posts) { webInterface.getCore().markPostKnown(post); diff --git a/src/main/java/net/pterodactylus/sone/web/WebInterface.java b/src/main/java/net/pterodactylus/sone/web/WebInterface.java index 6085ff8..adcc17e 100644 --- a/src/main/java/net/pterodactylus/sone/web/WebInterface.java +++ b/src/main/java/net/pterodactylus/sone/web/WebInterface.java @@ -20,6 +20,7 @@ package net.pterodactylus.sone.web; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; +import java.io.StringReader; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Collection; @@ -40,23 +41,29 @@ import net.pterodactylus.sone.data.Reply; import net.pterodactylus.sone.data.Sone; import net.pterodactylus.sone.freenet.L10nFilter; import net.pterodactylus.sone.freenet.wot.Identity; +import net.pterodactylus.sone.freenet.wot.Trust; import net.pterodactylus.sone.main.SonePlugin; import net.pterodactylus.sone.notify.ListNotification; import net.pterodactylus.sone.template.CollectionAccessor; import net.pterodactylus.sone.template.CssClassNameFilter; import net.pterodactylus.sone.template.GetPagePlugin; import net.pterodactylus.sone.template.IdentityAccessor; +import net.pterodactylus.sone.template.JavascriptFilter; import net.pterodactylus.sone.template.NotificationManagerAccessor; import net.pterodactylus.sone.template.PostAccessor; import net.pterodactylus.sone.template.ReplyAccessor; import net.pterodactylus.sone.template.RequestChangeFilter; import net.pterodactylus.sone.template.SoneAccessor; import net.pterodactylus.sone.template.SubstringFilter; +import net.pterodactylus.sone.template.TrustAccessor; import net.pterodactylus.sone.web.ajax.CreatePostAjaxPage; import net.pterodactylus.sone.web.ajax.CreateReplyAjaxPage; import net.pterodactylus.sone.web.ajax.DeletePostAjaxPage; +import net.pterodactylus.sone.web.ajax.DeleteProfileFieldAjaxPage; import net.pterodactylus.sone.web.ajax.DeleteReplyAjaxPage; import net.pterodactylus.sone.web.ajax.DismissNotificationAjaxPage; +import net.pterodactylus.sone.web.ajax.DistrustAjaxPage; +import net.pterodactylus.sone.web.ajax.EditProfileFieldAjaxPage; import net.pterodactylus.sone.web.ajax.FollowSoneAjaxPage; import net.pterodactylus.sone.web.ajax.GetLikesAjaxPage; import net.pterodactylus.sone.web.ajax.GetPostAjaxPage; @@ -67,9 +74,12 @@ import net.pterodactylus.sone.web.ajax.LikeAjaxPage; import net.pterodactylus.sone.web.ajax.LockSoneAjaxPage; import net.pterodactylus.sone.web.ajax.MarkPostAsKnownPage; import net.pterodactylus.sone.web.ajax.MarkReplyAsKnownPage; +import net.pterodactylus.sone.web.ajax.MoveProfileFieldAjaxPage; +import net.pterodactylus.sone.web.ajax.TrustAjaxPage; import net.pterodactylus.sone.web.ajax.UnfollowSoneAjaxPage; import net.pterodactylus.sone.web.ajax.UnlikeAjaxPage; import net.pterodactylus.sone.web.ajax.UnlockSoneAjaxPage; +import net.pterodactylus.sone.web.ajax.UntrustAjaxPage; import net.pterodactylus.sone.web.page.PageToadlet; import net.pterodactylus.sone.web.page.PageToadletFactory; import net.pterodactylus.sone.web.page.StaticPage; @@ -163,6 +173,7 @@ public class WebInterface implements CoreListener { templateFactory.addAccessor(Reply.class, new ReplyAccessor(getCore(), templateFactory)); templateFactory.addAccessor(Identity.class, new IdentityAccessor(getCore())); templateFactory.addAccessor(NotificationManager.class, new NotificationManagerAccessor()); + templateFactory.addAccessor(Trust.class, new TrustAccessor()); templateFactory.addFilter("date", new DateFilter()); templateFactory.addFilter("l10n", new L10nFilter(getL10n())); templateFactory.addFilter("substring", new SubstringFilter()); @@ -170,6 +181,7 @@ public class WebInterface implements CoreListener { templateFactory.addFilter("change", new RequestChangeFilter()); templateFactory.addFilter("match", new MatchFilter()); templateFactory.addFilter("css", new CssClassNameFilter()); + templateFactory.addFilter("js", new JavascriptFilter()); templateFactory.addPlugin("getpage", new GetPagePlugin()); templateFactory.addPlugin("paginate", new PaginationPlugin()); templateFactory.setTemplateProvider(new ClassPathTemplateProvider(templateFactory)); @@ -466,6 +478,7 @@ public class WebInterface implements CoreListener { * Register all toadlets. */ private void registerToadlets() { + Template emptyTemplate = templateFactory.createTemplate(new StringReader("")); Template loginTemplate = templateFactory.createTemplate(createReader("/templates/login.html")); Template indexTemplate = templateFactory.createTemplate(createReader("/templates/index.html")); Template knownSonesTemplate = templateFactory.createTemplate(createReader("/templates/knownSones.html")); @@ -473,22 +486,17 @@ public class WebInterface implements CoreListener { Template createPostTemplate = templateFactory.createTemplate(createReader("/templates/createPost.html")); Template createReplyTemplate = templateFactory.createTemplate(createReader("/templates/createReply.html")); Template editProfileTemplate = templateFactory.createTemplate(createReader("/templates/editProfile.html")); + Template editProfileFieldTemplate = templateFactory.createTemplate(createReader("/templates/editProfileField.html")); + Template deleteProfileFieldTemplate = templateFactory.createTemplate(createReader("/templates/deleteProfileField.html")); Template viewSoneTemplate = templateFactory.createTemplate(createReader("/templates/viewSone.html")); Template viewPostTemplate = templateFactory.createTemplate(createReader("/templates/viewPost.html")); - Template likePostTemplate = templateFactory.createTemplate(createReader("/templates/like.html")); - Template unlikePostTemplate = templateFactory.createTemplate(createReader("/templates/unlike.html")); Template deletePostTemplate = templateFactory.createTemplate(createReader("/templates/deletePost.html")); Template deleteReplyTemplate = templateFactory.createTemplate(createReader("/templates/deleteReply.html")); - Template lockSoneTemplate = templateFactory.createTemplate(createReader("/templates/lockSone.html")); - Template unlockSoneTemplate = templateFactory.createTemplate(createReader("/templates/unlockSone.html")); - Template followSoneTemplate = templateFactory.createTemplate(createReader("/templates/followSone.html")); - Template unfollowSoneTemplate = templateFactory.createTemplate(createReader("/templates/unfollowSone.html")); Template deleteSoneTemplate = templateFactory.createTemplate(createReader("/templates/deleteSone.html")); Template noPermissionTemplate = templateFactory.createTemplate(createReader("/templates/noPermission.html")); - Template dismissNotificationTemplate = templateFactory.createTemplate(createReader("/templates/dismissNotification.html")); - Template logoutTemplate = templateFactory.createTemplate(createReader("/templates/logout.html")); Template optionsTemplate = templateFactory.createTemplate(createReader("/templates/options.html")); Template aboutTemplate = templateFactory.createTemplate(createReader("/templates/about.html")); + Template invalidTemplate = templateFactory.createTemplate(createReader("/templates/invalid.html")); Template postTemplate = templateFactory.createTemplate(createReader("/templates/include/viewPost.html")); Template replyTemplate = templateFactory.createTemplate(createReader("/templates/include/viewReply.html")); @@ -497,25 +505,31 @@ public class WebInterface implements CoreListener { pageToadlets.add(pageToadletFactory.createPageToadlet(new CreateSonePage(createSoneTemplate, this), "CreateSone")); pageToadlets.add(pageToadletFactory.createPageToadlet(new KnownSonesPage(knownSonesTemplate, this), "KnownSones")); pageToadlets.add(pageToadletFactory.createPageToadlet(new EditProfilePage(editProfileTemplate, this), "EditProfile")); + pageToadlets.add(pageToadletFactory.createPageToadlet(new EditProfileFieldPage(editProfileFieldTemplate, this))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new DeleteProfileFieldPage(deleteProfileFieldTemplate, this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new CreatePostPage(createPostTemplate, this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new CreateReplyPage(createReplyTemplate, this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new ViewSonePage(viewSoneTemplate, this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new ViewPostPage(viewPostTemplate, this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new LikePage(likePostTemplate, this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new UnlikePage(unlikePostTemplate, this))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new LikePage(emptyTemplate, this))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new UnlikePage(emptyTemplate, this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new DeletePostPage(deletePostTemplate, this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new DeleteReplyPage(deleteReplyTemplate, this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new LockSonePage(lockSoneTemplate, this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new UnlockSonePage(unlockSoneTemplate, this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new FollowSonePage(followSoneTemplate, this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new UnfollowSonePage(unfollowSoneTemplate, this))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new LockSonePage(emptyTemplate, this))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new UnlockSonePage(emptyTemplate, this))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new FollowSonePage(emptyTemplate, this))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new UnfollowSonePage(emptyTemplate, this))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new TrustPage(emptyTemplate, this))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new DistrustPage(emptyTemplate, this))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new UntrustPage(emptyTemplate, this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new DeleteSonePage(deleteSoneTemplate, this), "DeleteSone")); pageToadlets.add(pageToadletFactory.createPageToadlet(new LoginPage(loginTemplate, this), "Login")); - pageToadlets.add(pageToadletFactory.createPageToadlet(new LogoutPage(logoutTemplate, this), "Logout")); + pageToadlets.add(pageToadletFactory.createPageToadlet(new LogoutPage(emptyTemplate, this), "Logout")); pageToadlets.add(pageToadletFactory.createPageToadlet(new OptionsPage(optionsTemplate, this), "Options")); pageToadlets.add(pageToadletFactory.createPageToadlet(new AboutPage(aboutTemplate, this, SonePlugin.VERSION), "About")); pageToadlets.add(pageToadletFactory.createPageToadlet(new SoneTemplatePage("noPermission.html", noPermissionTemplate, "Page.NoPermission.Title", this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new DismissNotificationPage(dismissNotificationTemplate, this))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new DismissNotificationPage(emptyTemplate, this))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new SoneTemplatePage("invalid.html", invalidTemplate, "Page.Invalid.Title", this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new StaticPage("css/", "/static/css/", "text/css"))); pageToadlets.add(pageToadletFactory.createPageToadlet(new StaticPage("javascript/", "/static/javascript/", "text/javascript"))); pageToadlets.add(pageToadletFactory.createPageToadlet(new StaticPage("images/", "/static/images/", "image/png"))); @@ -534,9 +548,15 @@ public class WebInterface implements CoreListener { pageToadlets.add(pageToadletFactory.createPageToadlet(new UnlockSoneAjaxPage(this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new FollowSoneAjaxPage(this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new UnfollowSoneAjaxPage(this))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new TrustAjaxPage(this))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new DistrustAjaxPage(this))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new UntrustAjaxPage(this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new LikeAjaxPage(this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new UnlikeAjaxPage(this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new GetLikesAjaxPage(this))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new EditProfileFieldAjaxPage(this))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new DeleteProfileFieldAjaxPage(this))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new MoveProfileFieldAjaxPage(this))); ToadletContainer toadletContainer = sonePlugin.pluginRespirator().getToadletContainer(); toadletContainer.getPageMaker().addNavigationCategory("/Sone/index.html", "Navigation.Menu.Name", "Navigation.Menu.Tooltip", sonePlugin); diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/DeleteProfileFieldAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/DeleteProfileFieldAjaxPage.java new file mode 100644 index 0000000..383d2d4 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/ajax/DeleteProfileFieldAjaxPage.java @@ -0,0 +1,61 @@ +/* + * Sone - DeleteProfileFieldAjaxPage.java - Copyright © 2011 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 . + */ + +package net.pterodactylus.sone.web.ajax; + +import net.pterodactylus.sone.data.Profile; +import net.pterodactylus.sone.data.Profile.Field; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.util.json.JsonObject; + +/** + * AJAX page that lets the user delete a profile field. + * + * @author David ‘Bombe’ Roden + */ +public class DeleteProfileFieldAjaxPage extends JsonPage { + + /** + * Creates a new “delete profile field” AJAX page. + * + * @param webInterface + * The Sone web interface + */ + public DeleteProfileFieldAjaxPage(WebInterface webInterface) { + super("deleteProfileField.ajax", webInterface); + } + + /** + * {@inheritDoc} + */ + @Override + protected JsonObject createJsonObject(Request request) { + String fieldId = request.getHttpRequest().getParam("field"); + Sone currentSone = getCurrentSone(request.getToadletContext()); + Profile profile = currentSone.getProfile(); + Field field = profile.getFieldById(fieldId); + if (field == null) { + return createErrorJsonObject("invalid-field-id"); + } + profile.removeField(field); + currentSone.setProfile(profile); + webInterface.getCore().saveSone(currentSone); + return createSuccessJsonObject().put("field", new JsonObject().put("id", field.getId())); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/DismissNotificationAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/DismissNotificationAjaxPage.java index 283e924..08e3ee5 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/DismissNotificationAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/DismissNotificationAjaxPage.java @@ -55,4 +55,12 @@ public class DismissNotificationAjaxPage extends JsonPage { return createSuccessJsonObject(); } + /** + * {@inheritDoc} + */ + @Override + protected boolean requiresLogin() { + return false; + } + } diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/DistrustAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/DistrustAjaxPage.java new file mode 100644 index 0000000..1a6d28f --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/ajax/DistrustAjaxPage.java @@ -0,0 +1,66 @@ +/* + * Sone - TrustAjaxPage.java - Copyright © 2011 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 . + */ + +package net.pterodactylus.sone.web.ajax; + +import net.pterodactylus.sone.core.Core; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.freenet.wot.Trust; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.util.json.JsonObject; + +/** + * AJAX page that lets the user distrust a Sone. + * + * @see Core#distrustSone(Sone, Sone) + * @author David ‘Bombe’ Roden + */ +public class DistrustAjaxPage extends JsonPage { + + /** + * Creates a new “distrust Sone” AJAX handler. + * + * @param webInterface + * The Sone web interface + */ + public DistrustAjaxPage(WebInterface webInterface) { + super("distrustSone.ajax", webInterface); + } + + /** + * {@inheritDoc} + */ + @Override + protected JsonObject createJsonObject(Request request) { + Sone currentSone = getCurrentSone(request.getToadletContext(), false); + if (currentSone == null) { + return createErrorJsonObject("auth-required"); + } + String soneId = request.getHttpRequest().getParam("sone"); + Sone sone = webInterface.getCore().getSone(soneId, false); + if (sone == null) { + return createErrorJsonObject("invalid-sone-id"); + } + webInterface.getCore().distrustSone(currentSone, sone); + Trust trust = webInterface.getCore().getTrust(currentSone, sone); + if (trust == null) { + return createErrorJsonObject("wot-plugin"); + } + return createSuccessJsonObject().put("trustValue", trust.getExplicit()); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/EditProfileFieldAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/EditProfileFieldAjaxPage.java new file mode 100644 index 0000000..0036545 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/ajax/EditProfileFieldAjaxPage.java @@ -0,0 +1,72 @@ +/* + * Sone - EditProfileFieldAjaxPage.java - Copyright © 2011 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 . + */ + +package net.pterodactylus.sone.web.ajax; + +import net.pterodactylus.sone.data.Profile; +import net.pterodactylus.sone.data.Profile.Field; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.util.json.JsonObject; + +/** + * AJAX page that lets the user rename a profile field. + * + * @author David ‘Bombe’ Roden + */ +public class EditProfileFieldAjaxPage extends JsonPage { + + /** + * Creates a new “edit profile field” AJAX page. + * + * @param webInterface + * The Sone web interface + */ + public EditProfileFieldAjaxPage(WebInterface webInterface) { + super("editProfileField.ajax", webInterface); + } + + // + // JSONPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected JsonObject createJsonObject(Request request) { + String fieldId = request.getHttpRequest().getParam("field"); + Sone currentSone = getCurrentSone(request.getToadletContext()); + Profile profile = currentSone.getProfile(); + Field field = profile.getFieldById(fieldId); + if (field == null) { + return createErrorJsonObject("invalid-field-id"); + } + String name = request.getHttpRequest().getParam("name", "").trim(); + if (name.length() == 0) { + return createErrorJsonObject("invalid-parameter-name"); + } + Field existingField = profile.getFieldByName(name); + if ((existingField != null) && !existingField.equals(field)) { + return createErrorJsonObject("duplicate-field-name"); + } + field.setName(name); + currentSone.setProfile(profile); + return createSuccessJsonObject(); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/GetStatusAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/GetStatusAjaxPage.java index a358fb1..659c8d2 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/GetStatusAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/GetStatusAjaxPage.java @@ -120,6 +120,14 @@ public class GetStatusAjaxPage extends JsonPage { return false; } + /** + * {@inheritDoc} + */ + @Override + protected boolean requiresLogin() { + return false; + } + // // PRIVATE METHODS // diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/GetTranslationPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/GetTranslationPage.java index 36327ca..725b13a 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/GetTranslationPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/GetTranslationPage.java @@ -59,4 +59,12 @@ public class GetTranslationPage extends JsonPage { return false; } + /** + * {@inheritDoc} + */ + @Override + protected boolean requiresLogin() { + return false; + } + } diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/JsonPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/JsonPage.java index 605afaf..8d48bce 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/JsonPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/JsonPage.java @@ -137,6 +137,16 @@ public abstract class JsonPage implements Page { return true; } + /** + * Returns whether this page requires the user to be logged in. + * + * @return {@code true} if the user needs to be logged in to use this page, + * {@code false} otherwise + */ + protected boolean requiresLogin() { + return true; + } + // // PROTECTED METHODS // @@ -184,6 +194,11 @@ public abstract class JsonPage implements Page { return new Response(401, "Not authorized", "application/json", JsonUtils.format(new JsonObject().put("success", false).put("error", "auth-required"))); } } + if (requiresLogin()) { + if (getCurrentSone(request.getToadletContext(), false) == null) { + return new Response(401, "Not authorized", "application/json", JsonUtils.format(createErrorJsonObject("auth-required"))); + } + } JsonObject jsonObject = createJsonObject(request); return new Response(200, "OK", "application/json", JsonUtils.format(jsonObject)); } diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/LockSoneAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/LockSoneAjaxPage.java index 6c4ece0..bca9a35 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/LockSoneAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/LockSoneAjaxPage.java @@ -53,4 +53,12 @@ public class LockSoneAjaxPage extends JsonPage { return createSuccessJsonObject(); } + /** + * {@inheritDoc} + */ + @Override + protected boolean requiresLogin() { + return false; + } + } diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/MoveProfileFieldAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/MoveProfileFieldAjaxPage.java new file mode 100644 index 0000000..780e4d1 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/ajax/MoveProfileFieldAjaxPage.java @@ -0,0 +1,78 @@ +/* + * Sone - MoveProfileFieldAjaxPage.java - Copyright © 2011 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 . + */ + +package net.pterodactylus.sone.web.ajax; + +import net.pterodactylus.sone.data.Profile; +import net.pterodactylus.sone.data.Profile.Field; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.util.json.JsonObject; + +/** + * AJAX page that lets the user move a profile field up or down. + * + * @see Profile#moveFieldUp(Field) + * @see Profile#moveFieldDown(Field) + * @author David ‘Bombe’ Roden + */ +public class MoveProfileFieldAjaxPage extends JsonPage { + + /** + * Creates a new “move profile field” AJAX page. + * + * @param webInterface + * The Sone web interface + */ + public MoveProfileFieldAjaxPage(WebInterface webInterface) { + super("moveProfileField.ajax", webInterface); + } + + // + // JSONPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected JsonObject createJsonObject(Request request) { + Sone currentSone = getCurrentSone(request.getToadletContext()); + Profile profile = currentSone.getProfile(); + String fieldId = request.getHttpRequest().getParam("field"); + Field field = profile.getFieldById(fieldId); + if (field == null) { + return createErrorJsonObject("invalid-field-id"); + } + String direction = request.getHttpRequest().getParam("direction"); + try { + if ("up".equals(direction)) { + profile.moveFieldUp(field); + } else if ("down".equals(direction)) { + profile.moveFieldDown(field); + } else { + return createErrorJsonObject("invalid-direction"); + } + } catch (IllegalArgumentException iae1) { + return createErrorJsonObject("not-possible"); + } + currentSone.setProfile(profile); + webInterface.getCore().saveSone(currentSone); + return createSuccessJsonObject(); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/TrustAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/TrustAjaxPage.java new file mode 100644 index 0000000..d6e2750 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/ajax/TrustAjaxPage.java @@ -0,0 +1,66 @@ +/* + * Sone - TrustAjaxPage.java - Copyright © 2011 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 . + */ + +package net.pterodactylus.sone.web.ajax; + +import net.pterodactylus.sone.core.Core; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.freenet.wot.Trust; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.util.json.JsonObject; + +/** + * AJAX page that lets the user trust a Sone. + * + * @see Core#trustSone(Sone, Sone) + * @author David ‘Bombe’ Roden + */ +public class TrustAjaxPage extends JsonPage { + + /** + * Creates a new “trust Sone” AJAX handler. + * + * @param webInterface + * The Sone web interface + */ + public TrustAjaxPage(WebInterface webInterface) { + super("trustSone.ajax", webInterface); + } + + /** + * {@inheritDoc} + */ + @Override + protected JsonObject createJsonObject(Request request) { + Sone currentSone = getCurrentSone(request.getToadletContext(), false); + if (currentSone == null) { + return createErrorJsonObject("auth-required"); + } + String soneId = request.getHttpRequest().getParam("sone"); + Sone sone = webInterface.getCore().getSone(soneId, false); + if (sone == null) { + return createErrorJsonObject("invalid-sone-id"); + } + webInterface.getCore().trustSone(currentSone, sone); + Trust trust = webInterface.getCore().getTrust(currentSone, sone); + if (trust == null) { + return createErrorJsonObject("wot-plugin"); + } + return createSuccessJsonObject().put("trustValue", trust.getExplicit()); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/UnlockSoneAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/UnlockSoneAjaxPage.java index 02682b2..e1c408c 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/UnlockSoneAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/UnlockSoneAjaxPage.java @@ -53,4 +53,12 @@ public class UnlockSoneAjaxPage extends JsonPage { return createSuccessJsonObject(); } + /** + * {@inheritDoc} + */ + @Override + protected boolean requiresLogin() { + return false; + } + } diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/UntrustAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/UntrustAjaxPage.java new file mode 100644 index 0000000..32c6229 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/ajax/UntrustAjaxPage.java @@ -0,0 +1,66 @@ +/* + * Sone - TrustAjaxPage.java - Copyright © 2011 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 . + */ + +package net.pterodactylus.sone.web.ajax; + +import net.pterodactylus.sone.core.Core; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.freenet.wot.Trust; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.util.json.JsonObject; + +/** + * AJAX page that lets the user untrust a Sone. + * + * @see Core#untrustSone(Sone, Sone) + * @author David ‘Bombe’ Roden + */ +public class UntrustAjaxPage extends JsonPage { + + /** + * Creates a new “untrust Sone” AJAX handler. + * + * @param webInterface + * The Sone web interface + */ + public UntrustAjaxPage(WebInterface webInterface) { + super("untrustSone.ajax", webInterface); + } + + /** + * {@inheritDoc} + */ + @Override + protected JsonObject createJsonObject(Request request) { + Sone currentSone = getCurrentSone(request.getToadletContext(), false); + if (currentSone == null) { + return createErrorJsonObject("auth-required"); + } + String soneId = request.getHttpRequest().getParam("sone"); + Sone sone = webInterface.getCore().getSone(soneId, false); + if (sone == null) { + return createErrorJsonObject("invalid-sone-id"); + } + webInterface.getCore().untrustSone(currentSone, sone); + Trust trust = webInterface.getCore().getTrust(currentSone, sone); + if (trust == null) { + return createErrorJsonObject("wot-plugin"); + } + return createSuccessJsonObject().put("trustValue", trust.getExplicit()); + } + +} diff --git a/src/main/resources/i18n/sone.en.properties b/src/main/resources/i18n/sone.en.properties index 740cf45..76d8941 100644 --- a/src/main/resources/i18n/sone.en.properties +++ b/src/main/resources/i18n/sone.en.properties @@ -27,6 +27,10 @@ Page.Options.Page.Title=Options Page.Options.Page.Description=These options influence the runtime behaviour of the Sone plugin. Page.Options.Section.RuntimeOptions.Title=Runtime Behaviour Page.Options.Option.InsertionDelay.Description=The number of seconds the Sone inserter waits after a modification of a Sone before it is being inserted. +Page.Options.Section.TrustOptions.Title=Trust Settings +Page.Options.Option.PositiveTrust.Description=The amount of positive trust you want to assign to other Sones by clicking the checkmark below a post or reply. +Page.Options.Option.NegativeTrust.Description=The amount of trust you want to assign to other Sones by clicking the red X below a post or reply. This value should be negative. +Page.Options.Option.TrustComment.Description=The comment that will be set in the web of trust for any trust you assign from Sone. Page.Options.Section.RescueOptions.Title=Rescue Settings Page.Options.Option.SoneRescueMode.Description=Try to rescue your Sones at the next start of the Sone plugin. This will read your all your old Sones from Freenet and ignore any disappearing postings and replies. You have to unlock your local Sones after they have been restored and you have to manually disable the rescue mode once you are satisfied with what has been restored! Page.Options.Section.Cleaning.Title=Clean Up @@ -70,8 +74,32 @@ Page.EditProfile.Birthday.Title=Birthday Page.EditProfile.Birthday.Label.Day=Day: Page.EditProfile.Birthday.Label.Month=Month: Page.EditProfile.Birthday.Label.Year=Year: -Page.EditProfile.Page.Status.Changed=Your changes have been saved and will be inserted shortly. +Page.EditProfile.Fields.Title=Custom Fields +Page.EditProfile.Fields.Description=Here you can enter custom fields into your profile. These fields can contain anything you want and be as terse or as verbose as you wish. Just remember that when it comes to anonymity, sometimes less is more. +Page.EditProfile.Fields.Button.Edit=edit +Page.EditProfile.Fields.Button.MoveUp=move up +Page.EditProfile.Fields.Button.MoveDown=move down +Page.EditProfile.Fields.Button.Delete=delete +Page.EditProfile.Fields.Button.ReallyDelete=really delete +Page.EditProfile.Fields.AddField.Title=Add Field +Page.EditProfile.Fields.AddField.Label.Name=Name: +Page.EditProfile.Fields.AddField.Button.AddField=Add Field Page.EditProfile.Button.Save=Save Profile +Page.EditProfile.Error.DuplicateFieldName=The field name “{fieldName}” does already exist. + +Page.EditProfileField.Title=Edit Profile Field - Sone +Page.EditProfileField.Page.Title=Edit Profile Field +Page.EditProfileField.Text=Enter a new name for this profile field. +Page.EditProfileField.Error.DuplicateFieldName=The field name you entered does already exist. +Page.EditProfileField.Button.Save=Change +Page.EditProfileField.Button.Reset=Revert to old name +Page.EditProfileField.Button.Cancel=Do not change name + +Page.DeleteProfileField.Title=Delete Profile Field - Sone +Page.DeleteProfileField.Page.Title=Delete Profile Field +Page.DeleteProfileField.Text=Do you really want to delete this profile field? +Page.DeleteProfileField.Button.Yes=Yes, delete +Page.DeleteProfileField.Button.No=No, do not delete Page.CreatePost.Title=Create Post - Sone Page.CreatePost.Page.Title=Create Post @@ -92,6 +120,8 @@ Page.ViewSone.UnknownSone.Description=This Sone has not yet been retrieved. Plea Page.ViewSone.WriteAMessage=You can write a message to this Sone here. Please note that everybody will be able to read this message! Page.ViewSone.PostList.Title=Posts by {sone} Page.ViewSone.PostList.Text.NoPostYet=This Sone has not yet posted anything. +Page.ViewSone.Profile.Title=Profile +Page.ViewSone.Profile.Label.Name=Name Page.ViewPost.Title=View Post - Sone Page.ViewPost.Page.Title=View Post by {sone} @@ -121,6 +151,12 @@ Page.FollowSone.Title=Follow Sone - Sone Page.UnfollowSone.Title=Unfollow Sone - Sone +Page.Trust.Title=Trust Sone - Sone + +Page.Distrust.Title=Distrust Sone - Sone + +Page.Untrust.Title=Untrust Sone - Sone + Page.NoPermission.Title=Unauthorized Access - Sone Page.NoPermission.Page.Title=Unauthorized Access Page.NoPermission.Text.NoPermission=You tried to do something that you do not have sufficient authorization for. Please refrain from such actions in the future or we will be forced to take counter-measures! @@ -132,6 +168,10 @@ Page.WotPluginMissing.Text.LoadPlugin=Please load the Web of Trust plugin in the Page.Logout.Title=Logout - Sone +Page.Invalid.Title=Invalid Action Performed +Page.Invalid.Page.Title=Invalid Action Performed +Page.Invalid.Text=An invalid action was performed, or the action was valid but the parameters were not. Please go back to the {link}index page{/link} and try again. If the error persists you have probably found a bug. + View.CreateSone.Text.WotIdentityRequired=To create a Sone you need an identity from the {link}Web of Trust plugin{/link}. View.CreateSone.Select.Default=Select an identity View.CreateSone.Text.NoIdentities=You do not have any Web of Trust identities. Please head over to the {link}Web of Trust plugin{/link} and create an identity. @@ -159,6 +199,10 @@ View.Post.Reply.DeleteLink=Delete View.Post.LikeLink=Like View.Post.UnlikeLink=Unlike +View.Trust.Tooltip.Trust=Trust this person +View.Trust.Tooltip.Distrust=Assign negative trust to this person +View.Trust.Tooltip.Untrust=Remove your trust assignment for this person + WebInterface.DefaultText.StatusUpdate=What’s on your mind? WebInterface.DefaultText.Message=Write a Message… WebInterface.DefaultText.Reply=Write a Reply… @@ -168,6 +212,7 @@ WebInterface.DefaultText.LastName=Last name WebInterface.DefaultText.BirthDay=Day WebInterface.DefaultText.BirthMonth=Month WebInterface.DefaultText.BirthYear=Year +WebInterface.DefaultText.FieldName=Field name WebInterface.DefaultText.Option.InsertionDelay=Time to wait after a Sone is modified before insert (in seconds) WebInterface.Confirmation.DeletePostButton=Yes, delete! WebInterface.Confirmation.DeleteReplyButton=Yes, delete! diff --git a/src/main/resources/static/css/sone.css b/src/main/resources/static/css/sone.css index 4f0c49e..f39cfef 100644 --- a/src/main/resources/static/css/sone.css +++ b/src/main/resources/static/css/sone.css @@ -1,7 +1,7 @@ /* Sone Main CSS File */ /* first, override some fproxy rules. */ -#sone .post .reply div,#sone .post .time,#sone .post .delete,#sone .post .show-reply-form, input[type=text], textarea { +#sone div, #sone span, #sone .post .time,#sone .post .delete,#sone .post .show-reply-form, input[type=text], textarea { font: inherit; } @@ -16,6 +16,10 @@ input[type=text], textarea { outline: none; } +input[type=text].short { + width: 25em; +} + textarea { height: 4em; } @@ -219,33 +223,57 @@ textarea { font-size: 85%; } +#sone .separator { + font: inherit; + color: rgb(28, 131, 191); +} + #sone .post .time { display: inline; color: #666; } -#sone .post .delete, #sone .post .likes, #sone .post .like, #sone .post .unlike { +#sone .post .delete, #sone .post .likes, #sone .post .like, #sone .post .unlike, #sone .post .trust, #sone .post .distrust, #sone .post .untrust { display: inline; font: inherit; + margin: 0px; } #sone .post .likes.hidden { display: none; } -#sone .post .like.hidden, #sone .post .unlike.hidden { +#sone .post .like.hidden, #sone .post .unlike.hidden, #sone .post .trust.hidden, #sone .post .distrust.hidden, #sone .post .untrust.hidden { display: none; } -#sone .post .delete button, #sone .post .like button, #sone .post .unlike button { +#sone .post .delete button, #sone .post .like button, #sone .post .unlike button, #sone .post .trust button, #sone .post .distrust button, #sone .post .untrust button { border: 0px; background: none; padding: 0px; color: rgb(28, 131, 191); font: inherit; + margin: 0px; +} + +#sone .post .delete button.confirm { + color: red; + font-weight: bold; } -#sone .post .delete button:hover, #sone .post .like button:hover, #sone .post .unlike button:hover { +#sone .post .trust button { + color: rgb(0, 128, 0); +} + +#sone .post .distrust button { + color: rgb(255, 0, 0); +} + +#sone .post .untrust button { + color: rgb(64, 64, 64); +} + +#sone .post .delete button:hover, #sone .post .like button:hover, #sone .post .unlike button:hover, #sone .post .trust button:hover, #sone .post .distrust button:hover, #sone .post .untrust button:hover { border: 0px; background: none; padding: 0px; @@ -253,10 +281,6 @@ textarea { cursor: pointer; } -#sone .post .delete:before, #sone .post .likes:before, #sone .post .like:before, #sone .post .unlike:before { - content: ' ‧ '; -} - #sone .post .likes span { font: inherit; color: green; @@ -321,10 +345,6 @@ textarea { color: rgb(255, 172, 0); } -#sone .post .show-reply-form:before { - content: ' ‧ '; -} - #sone .post .create-reply { clear: both; background-color: #f0f0ff; @@ -438,8 +458,40 @@ textarea { display: inline; } -#sone #create-sone { +#sone .profile-field, #sone #edit-profile button[type=submit], #sone #delete-profile-field { + margin-top: 1em; +} + +#sone .profile-field .name { + display: inline; + font-weight: bold; +} +#sone .profile-field .name.hidden { + display: none; +} + +#sone .profile-field button.confirm { + font-weight: bold; + color: #080; +} + +#sone .profile-field button.cancel { + font-weight: bold; + color: red; +} + +#sone .profile-field .value { + margin-left: 2em; +} + +#sone #edit-profile .profile-field .value { + margin-left: inherit; +} + +#sone .profile-field .edit-field-name, #sone .profile-field .move-up-field, #sone .profile-field .move-down-field, #sone .profile-field .delete-field-name { + float: right; + margin-top: -1ex; } #sone #tail { @@ -525,6 +577,6 @@ textarea { } #sone .confirm { - font-weight: bold !important; - color: red !important; + font-weight: bold; + color: red; } diff --git a/src/main/resources/static/javascript/sone.js b/src/main/resources/static/javascript/sone.js index 3c229e0..e0c8162 100644 --- a/src/main/resources/static/javascript/sone.js +++ b/src/main/resources/static/javascript/sone.js @@ -47,6 +47,7 @@ function registerInputTextareaSwap(inputElement, defaultText, inputFieldName, op textarea.show(); } $(inputField.get(0).form).submit(function() { + inputField.attr("disabled", "disabled"); if (!optional && (textarea.val() == "")) { return false; } @@ -68,6 +69,7 @@ function addCommentLink(postId, element, insertAfterThisElement) { return; } commentElement = (function(postId) { + separator = $(" · ").addClass("separator"); var commentElement = $("
Comment
").addClass("show-reply-form").click(function() { markPostAsKnown(getPostElement(this)); replyElement = $("#sone .post#" + postId + " .create-reply"); @@ -87,6 +89,7 @@ function addCommentLink(postId, element, insertAfterThisElement) { return commentElement; })(postId); $(insertAfterThisElement).after(commentElement.clone(true)); + $(insertAfterThisElement).after(separator); } var translations = {}; @@ -301,6 +304,17 @@ function getPostTime(element) { return getPostElement(element).find(".post-time").text(); } +/** + * Returns the author of the post the given element belongs to. + * + * @param element + * The element whose post to get the author for + * @returns The ID of the authoring Sone + */ +function getPostAuthor(element) { + return getPostElement(element).find(".post-author").text(); +} + function getReplyElement(element) { return $(element).closest(".reply"); } @@ -313,6 +327,17 @@ function getReplyTime(element) { return getReplyElement(element).find(".reply-time").text(); } +/** + * Returns the author of the reply the given element belongs to. + * + * @param element + * The element whose reply to get the author for + * @returns The ID of the authoring Sone + */ +function getReplyAuthor(element) { + return getReplyElement(element).find(".reply-author").text(); +} + function likePost(postId) { $.getJSON("like.ajax", { "type": "post", "post" : postId, "formPassword": getFormPassword() }, function(data, textStatus) { if ((data == null) || !data.success) { @@ -377,6 +402,74 @@ function unlikeReply(replyId) { }); } +/** + * Trusts the Sone with the given ID. + * + * @param soneId + * The ID of the Sone to trust + */ +function trustSone(soneId) { + $.getJSON("trustSone.ajax", { "formPassword" : getFormPassword(), "sone" : soneId }, function(data, textStatus) { + if ((data != null) && data.success) { + updateTrustControls(soneId, data.trustValue); + } + }); +} + +/** + * Distrusts the Sone with the given ID, i.e. assigns a negative trust value. + * + * @param soneId + * The ID of the Sone to distrust + */ +function distrustSone(soneId) { + $.getJSON("distrustSone.ajax", { "formPassword" : getFormPassword(), "sone" : soneId }, function(data, textStatus) { + if ((data != null) && data.success) { + updateTrustControls(soneId, data.trustValue); + } + }); +} + +/** + * Untrusts the Sone with the given ID, i.e. removes any trust assignment. + * + * @param soneId + * The ID of the Sone to untrust + */ +function untrustSone(soneId) { + $.getJSON("untrustSone.ajax", { "formPassword" : getFormPassword(), "sone" : soneId }, function(data, textStatus) { + if ((data != null) && data.success) { + updateTrustControls(soneId, data.trustValue); + } + }); +} + +/** + * Updates the trust controls for all posts and replies of the given Sone, + * according to the given trust value. + * + * @param soneId + * The ID of the Sone to update all trust controls for + * @param trustValue + * The trust value for the Sone + */ +function updateTrustControls(soneId, trustValue) { + $("#sone .post").each(function() { + if (getPostAuthor(this) == soneId) { + getPostElement(this).find(".post-trust").toggleClass("hidden", trustValue != null); + getPostElement(this).find(".post-distrust").toggleClass("hidden", (trustValue != null) && (trustValue < 0)); + getPostElement(this).find(".post-untrust").toggleClass("hidden", trustValue == null); + } + }); + $("#sone .reply").each(function() { + if (getReplyAuthor(this) == soneId) { + getReplyElement(this).find(".reply-trust").toggleClass("hidden", trustValue != null); + getReplyElement(this).find(".reply-distrust").toggleClass("hidden", (trustValue != null) && (trustValue < 0)); + getReplyElement(this).find(".reply-untrust").toggleClass("hidden", trustValue == null); + } + }); +} + function updateReplyLikes(replyId) { $.getJSON("getLikes.ajax", { "type": "reply", "reply": replyId }, function(data, textStatus) { if ((data != null) && data.success) { @@ -484,6 +577,20 @@ function ajaxifyPost(postElement) { return false; }); + /* convert trust control buttons to javascript functions. */ + $(postElement).find(".post-trust").submit(function() { + trustSone(getPostAuthor(this)); + return false; + }); + $(postElement).find(".post-distrust").submit(function() { + distrustSone(getPostAuthor(this)); + return false; + }); + $(postElement).find(".post-untrust").submit(function() { + untrustSone(getPostAuthor(this)); + return false; + }); + /* add “comment” link. */ addCommentLink(getPostId(postElement), postElement, $(postElement).find(".post-status-line .time")); @@ -500,7 +607,10 @@ function ajaxifyPost(postElement) { }); /* mark everything as known on click. */ - $(postElement).click(function() { + $(postElement).click(function(event) { + if ($(event.target).hasClass("click-to-show")) { + return false; + } markPostAsKnown(this); }); @@ -534,6 +644,20 @@ function ajaxifyReply(replyElement) { })(replyElement); addCommentLink(getPostId(replyElement), replyElement, $(replyElement).find(".reply-status-line .time")); + /* convert trust control buttons to javascript functions. */ + $(replyElement).find(".reply-trust").submit(function() { + trustSone(getReplyAuthor(this)); + return false; + }); + $(replyElement).find(".reply-distrust").submit(function() { + distrustSone(getReplyAuthor(this)); + return false; + }); + $(replyElement).find(".reply-untrust").submit(function() { + untrustSone(getReplyAuthor(this)); + return false; + }); + /* mark post and all replies as known on click. */ $(replyElement).click(function() { markPostAsKnown(getPostElement(this)); @@ -793,6 +917,7 @@ function markPostAsKnown(postElements) { (function(postElement) { $.getJSON("markPostAsKnown.ajax", {"formPassword": getFormPassword(), "post": getPostId(postElement)}, function(data, textStatus) { $(postElement).removeClass("new"); + $(".click-to-show", postElement).removeClass("new"); }); })(postElement); } @@ -862,6 +987,80 @@ function showNotificationDetails(notificationId) { $("#sone .notification#" + notificationId + " .short-text").hide(); } +/** + * Deletes the field with the given ID from the profile. + * + * @param fieldId + * The ID of the field to delete + */ +function deleteProfileField(fieldId) { + $.getJSON("deleteProfileField.ajax", {"formPassword": getFormPassword(), "field": fieldId}, function(data, textStatus) { + if (data && data.success) { + $("#sone .profile-field#" + data.field.id).slideUp(); + } + }); +} + +/** + * Renames a profile field. + * + * @param fieldId + * The ID of the field to rename + * @param newName + * The new name of the field + * @param successFunction + * Called when the renaming was successful + */ +function editProfileField(fieldId, newName, successFunction) { + $.getJSON("editProfileField.ajax", {"formPassword": getFormPassword(), "field": fieldId, "name": newName}, function(data, textStatus) { + if (data && data.success) { + successFunction(); + } + }); +} + +/** + * Moves the profile field with the given ID one slot in the given direction. + * + * @param fieldId + * The ID of the field to move + * @param direction + * The direction to move in (“up” or “down”) + * @param successFunction + * Function to call on success + */ +function moveProfileField(fieldId, direction, successFunction) { + $.getJSON("moveProfileField.ajax", {"formPassword": getFormPassword(), "field": fieldId, "direction": direction}, function(data, textStatus) { + if (data && data.success) { + successFunction(); + } + }); +} + +/** + * Moves the profile field with the given ID up one slot. + * + * @param fieldId + * The ID of the field to move + * @param successFunction + * Function to call on success + */ +function moveProfileFieldUp(fieldId, successFunction) { + moveProfileField(fieldId, "up", successFunction); +} + +/** + * Moves the profile field with the given ID down one slot. + * + * @param fieldId + * The ID of the field to move + * @param successFunction + * Function to call on success + */ +function moveProfileFieldDown(fieldId, successFunction) { + moveProfileField(fieldId, "down", successFunction); +} + // // EVERYTHING BELOW HERE IS EXECUTED AFTER LOADING THE PAGE // diff --git a/src/main/resources/templates/deleteProfileField.html b/src/main/resources/templates/deleteProfileField.html new file mode 100644 index 0000000..006e1ff --- /dev/null +++ b/src/main/resources/templates/deleteProfileField.html @@ -0,0 +1,19 @@ +<%include include/head.html> + +

<%= Page.DeleteProfileField.Page.Title|l10n|html>

+ +

<%= Page.DeleteProfileField.Text|l10n|html>

+ +
+
<% field.name|html>
+
<% field.value|html>
+
+ +
+ + + + +
+ +<%include include/tail.html> diff --git a/src/main/resources/templates/dismissNotification.html b/src/main/resources/templates/dismissNotification.html deleted file mode 100644 index 196af72..0000000 --- a/src/main/resources/templates/dismissNotification.html +++ /dev/null @@ -1,3 +0,0 @@ -<%include include/head.html> - -<%include include/tail.html> diff --git a/src/main/resources/templates/editProfile.html b/src/main/resources/templates/editProfile.html index d1d5943..8a1b7d7 100644 --- a/src/main/resources/templates/editProfile.html +++ b/src/main/resources/templates/editProfile.html @@ -1,7 +1,14 @@ <%include include/head.html> @@ -31,10 +145,6 @@

<%= Page.EditProfile.Page.Description|l10n|html>

<%= Page.EditProfile.Page.Hint.Optionality|l10n|html>

- <%if changed> -

<%= Page.EditProfile.Page.Status.Changed|l10n|html>

- <%/if> -
@@ -71,7 +181,47 @@
- + +
+ +

<%= Page.EditProfile.Fields.Title|l10n|html>

+ +

<%= Page.EditProfile.Fields.Description|l10n|html>

+ + <%foreach fields field fieldLoop> +
+
<% field.name|html>
+ +
+
+
+
+
+
+ + <%if fieldLoop.last> +
+ +
+ <%/if> + <%/foreach> + +
+ +
+ + + +

<%= Page.EditProfile.Fields.AddField.Title|l10n|html>

+ + <%if duplicateFieldName> +

<%= Page.EditProfile.Error.DuplicateFieldName|l10n|replace needle="{fieldName}" replacementKey="fieldName"|html>

+ <%/if> + +
+ + +
diff --git a/src/main/resources/templates/editProfileField.html b/src/main/resources/templates/editProfileField.html new file mode 100644 index 0000000..6030f71 --- /dev/null +++ b/src/main/resources/templates/editProfileField.html @@ -0,0 +1,24 @@ +<%include include/head.html> + +

<%= Page.EditProfileField.Page.Title|l10n|html>

+ +

<%= Page.EditProfileField.Text|l10n|html>

+ + <%if duplicateFieldName> +

<%= Page.EditProfileField.Error.DuplicateFieldName|l10n|html>

+ <%/if> + +
+ + +
+ + +
+

+ + +

+
+ +<%include include/tail.html> diff --git a/src/main/resources/templates/followSone.html b/src/main/resources/templates/followSone.html deleted file mode 100644 index 196af72..0000000 --- a/src/main/resources/templates/followSone.html +++ /dev/null @@ -1,3 +0,0 @@ -<%include include/head.html> - -<%include include/tail.html> diff --git a/src/main/resources/templates/include/viewPost.html b/src/main/resources/templates/include/viewPost.html index cd6cc5c..a19ee73 100644 --- a/src/main/resources/templates/include/viewPost.html +++ b/src/main/resources/templates/include/viewPost.html @@ -1,6 +1,7 @@
+
Avatar Image
@@ -19,6 +20,7 @@
+ ·
↑
<%ifnull ! currentSone> <%/if> + <%if !post.sone.current> + · +
+ + + + +
+
+ + + + +
+
+ + + + +
+ <%/if> <%if post.sone.current> + ·
diff --git a/src/main/resources/templates/include/viewReply.html b/src/main/resources/templates/include/viewReply.html index f65a201..8c6884b 100644 --- a/src/main/resources/templates/include/viewReply.html +++ b/src/main/resources/templates/include/viewReply.html @@ -1,6 +1,7 @@
+
Avatar Image
@@ -11,6 +12,7 @@
<% reply.time|date format="MMM d, yyyy, HH:mm:ss">
+ ·
↑
<%ifnull ! currentSone> @@ -28,7 +30,29 @@ <%/if> + <%if !reply.sone.current> + · +
+ + + + +
+
+ + + + +
+
+ + + + +
+ <%/if> <%if reply.sone.current> + ·
diff --git a/src/main/resources/templates/insert/sone.xml b/src/main/resources/templates/insert/sone.xml index 7d3c8aa..9ba037f 100644 --- a/src/main/resources/templates/insert/sone.xml +++ b/src/main/resources/templates/insert/sone.xml @@ -16,6 +16,14 @@ <% currentSone.profile.birthDay|xml> <% currentSone.profile.birthMonth|xml> <% currentSone.profile.birthYear|xml> + + <%foreach currentSone.profile.fields field> + + <% field.key|xml> + <% field.value|xml> + + <%/foreach> + diff --git a/src/main/resources/templates/invalid.html b/src/main/resources/templates/invalid.html new file mode 100644 index 0000000..d838134 --- /dev/null +++ b/src/main/resources/templates/invalid.html @@ -0,0 +1,7 @@ +<%include include/head.html> + +

<%= Page.Invalid.Page.Title|l10n|html>

+ +

<%= Page.Invalid.Text|l10n|html|replace needle="{link}" replacement=''|replace needle="{/link}" replacement=''>

+ +<%include include/tail.html> diff --git a/src/main/resources/templates/like.html b/src/main/resources/templates/like.html deleted file mode 100644 index 196af72..0000000 --- a/src/main/resources/templates/like.html +++ /dev/null @@ -1,3 +0,0 @@ -<%include include/head.html> - -<%include include/tail.html> diff --git a/src/main/resources/templates/lockSone.html b/src/main/resources/templates/lockSone.html deleted file mode 100644 index 196af72..0000000 --- a/src/main/resources/templates/lockSone.html +++ /dev/null @@ -1,3 +0,0 @@ -<%include include/head.html> - -<%include include/tail.html> diff --git a/src/main/resources/templates/logout.html b/src/main/resources/templates/logout.html deleted file mode 100644 index 196af72..0000000 --- a/src/main/resources/templates/logout.html +++ /dev/null @@ -1,3 +0,0 @@ -<%include include/head.html> - -<%include include/tail.html> diff --git a/src/main/resources/templates/options.html b/src/main/resources/templates/options.html index f0baa50..5c47764 100644 --- a/src/main/resources/templates/options.html +++ b/src/main/resources/templates/options.html @@ -20,6 +20,17 @@

<%= Page.Options.Option.InsertionDelay.Description|l10n|html>

+

<%= Page.Options.Section.TrustOptions.Title|l10n|html>

+ +

<%= Page.Options.Option.PositiveTrust.Description|l10n|html>

+

+ +

<%= Page.Options.Option.NegativeTrust.Description|l10n|html>

+

+ +

<%= Page.Options.Option.TrustComment.Description|l10n|html>

+

+

<%= Page.Options.Section.RescueOptions.Title|l10n|html>

<%= Page.Options.Option.SoneRescueMode.Description|l10n|html>

diff --git a/src/main/resources/templates/unfollowSone.html b/src/main/resources/templates/unfollowSone.html deleted file mode 100644 index 196af72..0000000 --- a/src/main/resources/templates/unfollowSone.html +++ /dev/null @@ -1,3 +0,0 @@ -<%include include/head.html> - -<%include include/tail.html> diff --git a/src/main/resources/templates/unlike.html b/src/main/resources/templates/unlike.html deleted file mode 100644 index 196af72..0000000 --- a/src/main/resources/templates/unlike.html +++ /dev/null @@ -1,3 +0,0 @@ -<%include include/head.html> - -<%include include/tail.html> diff --git a/src/main/resources/templates/unlockSone.html b/src/main/resources/templates/unlockSone.html deleted file mode 100644 index 196af72..0000000 --- a/src/main/resources/templates/unlockSone.html +++ /dev/null @@ -1,3 +0,0 @@ -<%include include/head.html> - -<%include include/tail.html> diff --git a/src/main/resources/templates/viewSone.html b/src/main/resources/templates/viewSone.html index 884fa0f..98551b3 100644 --- a/src/main/resources/templates/viewSone.html +++ b/src/main/resources/templates/viewSone.html @@ -19,7 +19,23 @@ <%if ! sone.current> <%include include/viewSone.html> + <%/if> + +

<%= Page.ViewSone.Profile.Title|l10n|html>

+
+
<%= Page.ViewSone.Profile.Label.Name|l10n|html>
+ +
+ + <%foreach sone.profile.fields field> +
+
<% field.name|html>
+
<% field.value|html>
+
+ <%/foreach> + + <%if ! sone.current>

<%= Page.ViewSone.WriteAMessage|l10n|html>

@@ -31,7 +47,6 @@ <%/if> -

<%= Page.ViewSone.PostList.Title|l10n|insert needle="{sone}" key=sone.niceName|html>