<modelVersion>4.0.0</modelVersion>
<groupId>net.pterodactylus</groupId>
<artifactId>sone</artifactId>
- <version>0.3.7</version>
+ <version>0.4</version>
<dependencies>
<dependency>
<groupId>net.pterodactylus</groupId>
<artifactId>utils</artifactId>
- <version>0.7.7</version>
+ <version>0.7.8</version>
</dependency>
<dependency>
<groupId>junit</groupId>
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;
/** All known replies. */
private Set<String> knownReplies = new HashSet<String>();
+ /** Trusted identities, sorted by own identities. */
+ private Map<OwnIdentity, Set<Identity>> trustedIdentities = Collections.synchronizedMap(new HashMap<OwnIdentity, Set<Identity>>());
+
/**
* Creates a new core.
*
}
/**
+ * 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
* @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;
}
}
/**
+ * 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
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) {
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<Post> posts = new HashSet<Post>();
while (true) {
}
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());
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()) {
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);
}
}
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());
}
}));
+ options.addIntegerOption("PositiveTrust", new DefaultOption<Integer>(75));
+ options.addIntegerOption("NegativeTrust", new DefaultOption<Integer>(-100));
+ options.addStringOption("TrustComment", new DefaultOption<String>("Set from Sone Web Interface"));
options.addBooleanOption("SoneRescueMode", new DefaultOption<Boolean>(false));
options.addBooleanOption("ClearOnNextRestart", new DefaultOption<Boolean>(false));
options.addBooleanOption("ReallyClearOnNextRestart", new DefaultOption<Boolean>(false));
}
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. */
public void ownIdentityAdded(OwnIdentity ownIdentity) {
logger.log(Level.FINEST, "Adding OwnIdentity: " + ownIdentity);
if (ownIdentity.hasContext("Sone")) {
+ trustedIdentities.put(ownIdentity, Collections.synchronizedSet(new HashSet<Identity>()));
addLocalSone(ownIdentity);
}
}
@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);
}
* {@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();
* {@inheritDoc}
*/
@Override
- public void identityRemoved(Identity identity) {
- /* TODO */
+ public void identityRemoved(OwnIdentity ownIdentity, Identity identity) {
+ trustedIdentities.get(ownIdentity).remove(identity);
}
//
/** Holds all {@link Integer} {@link Option}s. */
private final Map<String, Option<Integer>> integerOptions = Collections.synchronizedMap(new HashMap<String, Option<Integer>>());
+ /** Holds all {@link String} {@link Option}s. */
+ private final Map<String, Option<String>> stringOptions = Collections.synchronizedMap(new HashMap<String, Option<String>>());
+
/**
* Adds a boolean option.
*
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<String> addStringOption(String name, Option<String> 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<String> getStringOption(String name) {
+ return stringOptions.get(name);
+ }
+
}
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<Post> posts = new HashSet<Post>();
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;
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface Fingerprintable {
+
+ /**
+ * Returns the fingerprint of this object.
+ *
+ * @return The fingerprint of this object
+ */
+ public String getFingerprint();
+
+}
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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-public class Profile {
-
- /** Whether the profile was modified. */
- private volatile boolean modified;
+public class Profile implements Fingerprintable {
/** The first name. */
private volatile String firstName;
/** The year of the birth date. */
private volatile Integer birthYear;
+ /** Additional fields in the profile. */
+ private final List<Field> fields = Collections.synchronizedList(new ArrayList<Field>());
+
/**
* Creates a new empty profile.
*/
this.birthDay = profile.birthDay;
this.birthMonth = profile.birthMonth;
this.birthYear = profile.birthYear;
+ this.fields.addAll(profile.fields);
}
//
//
/**
- * 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
* @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;
}
* @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;
}
* @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;
}
* @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;
}
* @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;
}
* @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<Field> getFields() {
+ return new ArrayList<Field>(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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+ 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();
+ }
+
+ }
+
}
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-public class Sone {
+public class Sone implements Fingerprintable {
/** comparator that sorts Sones by their nice name. */
public static final Comparator<Sone> NICE_NAME_COMPARATOR = new Comparator<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()) {
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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);
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class ConnectorListenerManager extends AbstractListenerManager<PluginConnector, ConnectorListener> {
+
+ /**
+ * 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);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class PluginConnector implements FredPluginTalker {
+
+ /** The plugin respirator. */
+ private final PluginRespirator pluginRespirator;
+
+ /** Connector listener managers for all plugin connections. */
+ private final Map<Pair<String, String>, ConnectorListenerManager> connectorListenerManagers = Collections.synchronizedMap(new HashMap<Pair<String, String>, 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<String, String>(pluginName, identifier));
+ if (create && (connectorListenerManager == null)) {
+ connectorListenerManager = new ConnectorListenerManager(this);
+ connectorListenerManagers.put(new Pair<String, String>(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);
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.plugin;
+
+import net.pterodactylus.sone.freenet.wot.WebOfTrustException;
+
+/**
+ * Exception that signals an error when communicating with a plugin.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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);
+ }
+
+}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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);
-
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class ConnectorListenerManager extends AbstractListenerManager<PluginConnector, ConnectorListener> {
-
- /**
- * 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);
- }
- }
-
-}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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<String> contexts = Collections.synchronizedSet(new HashSet<String>());
+
+ /** The properties of the identity. */
+ private final Map<String, String> properties = Collections.synchronizedMap(new HashMap<String, String>());
+
+ /** Cached trust. */
+ private final WritableCache<OwnIdentity, Trust> trustCache = new MemoryCache<OwnIdentity, Trust>(new ValueRetriever<OwnIdentity, Trust>() {
+
+ @Override
+ @SuppressWarnings("synthetic-access")
+ public CacheItem<Trust> retrieve(OwnIdentity ownIdentity) throws CacheException {
+ try {
+ return new DefaultCacheItem<Trust>(webOfTrustConnector.getTrust(ownIdentity, DefaultIdentity.this));
+ } catch (PluginException pe1) {
+ throw new CacheException("Could not retrieve trust for OwnIdentity: " + ownIdentity, pe1);
+ }
+ }
+
+ }, new TimedMap<OwnIdentity, CacheItem<Trust>>(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<String> getContexts() {
+ return Collections.unmodifiableSet(contexts);
+ }
+
+ /**
+ * Sets the contexts of this identity.
+ * <p>
+ * This method is only called by the {@link IdentityManager}.
+ *
+ * @param contexts
+ * The contexts to set
+ */
+ void setContextsPrivate(Set<String> 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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<String, String> getProperties() {
+ synchronized (properties) {
+ return Collections.unmodifiableMap(properties);
+ }
+ }
+
+ /**
+ * Sets all properties of this identity.
+ * <p>
+ * This method is only called by the {@link IdentityManager}.
+ *
+ * @param properties
+ * The new properties of this identity
+ */
+ void setPropertiesPrivate(Map<String, String> properties) {
+ synchronized (this.properties) {
+ this.properties.clear();
+ this.properties.putAll(properties);
+ }
+ }
+
+ /**
+ * Sets the property with the given name to the given value.
+ * <p>
+ * 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.
+ * <p>
+ * 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 + "]";
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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<String> 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<String, String> properties) throws WebOfTrustException {
+ for (Entry<String, String> 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<String, String> 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));
+ }
+ }
+
+}
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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-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<String> contexts = Collections.synchronizedSet(new HashSet<String>());
-
- /** The properties of the identity. */
- private final Map<String, String> properties = Collections.synchronizedMap(new HashMap<String, String>());
-
- /**
- * 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<String> getContexts() {
- return Collections.unmodifiableSet(contexts);
- }
-
- /**
- * Sets all contexts of this identity.
- * <p>
- * This method is only called by the {@link IdentityManager}.
- *
- * @param contexts
- * All contexts of the identity
- */
- void setContexts(Set<String> contexts) {
- this.contexts.clear();
- this.contexts.addAll(contexts);
- }
+ public Set<String> getContexts();
/**
* Returns whether this identity has the given context.
* @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.
- * <p>
- * 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.
- * <p>
- * 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<String, String> getProperties() {
- synchronized (properties) {
- return Collections.unmodifiableMap(properties);
- }
- }
-
- /**
- * Sets all properties of this identity.
- * <p>
- * This method is only called by the {@link IdentityManager}.
- *
- * @param properties
- * The new properties of this identity
- */
- void setProperties(Map<String, String> properties) {
- synchronized (this.properties) {
- this.properties.clear();
- this.properties.putAll(properties);
- }
- }
-
- /**
- * Sets the property with the given name to the given value.
- * <p>
- * 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<String, String> getProperties();
/**
* Returns the value of the property with the given name.
* 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.
- * <p>
- * 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);
}
/**
* 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);
}
/**
* Notifies all listeners that a new identity was discovered.
*
- * @see IdentityListener#identityAdded(Identity)
+ * @see IdentityListener#identityAdded(OwnIdentity, Identity)
+ * @param ownIdentity
+ * The own identity at the root of the trust tree
* @param identity
* The new identity
*/
- public void fireIdentityAdded(Identity identity) {
+ public void fireIdentityAdded(OwnIdentity ownIdentity, Identity identity) {
for (IdentityListener identityListener : getListeners()) {
- identityListener.identityAdded(identity);
+ identityListener.identityAdded(ownIdentity, identity);
}
}
/**
* Notifies all listeners that some properties of the identity have changed.
*
- * @see IdentityListener#identityUpdated(Identity)
+ * @see IdentityListener#identityUpdated(OwnIdentity, Identity)
+ * @param ownIdentity
+ * The own identity at the root of the trust tree
* @param identity
* The updated identity
*/
- public void fireIdentityUpdated(Identity identity) {
+ public void fireIdentityUpdated(OwnIdentity ownIdentity, Identity identity) {
for (IdentityListener identityListener : getListeners()) {
- identityListener.identityUpdated(identity);
+ identityListener.identityUpdated(ownIdentity, identity);
}
}
/**
* Notifies all listeners that an identity has gone away.
*
- * @see IdentityListener#identityRemoved(Identity)
+ * @see IdentityListener#identityRemoved(OwnIdentity, Identity)
+ * @param ownIdentity
+ * The own identity at the root of the trust tree
* @param identity
* The disappeared identity
*/
- public void fireIdentityRemoved(Identity identity) {
+ public void fireIdentityRemoved(OwnIdentity ownIdentity, Identity identity) {
for (IdentityListener identityListener : getListeners()) {
- identityListener.identityRemoved(identity);
+ identityListener.identityRemoved(ownIdentity, identity);
}
}
import java.util.logging.Level;
import java.util.logging.Logger;
+import net.pterodactylus.sone.freenet.plugin.PluginException;
import net.pterodactylus.util.logging.Logging;
import net.pterodactylus.util.service.AbstractService;
}
checkOwnIdentities(newOwnIdentities);
return ownIdentities;
- } catch (PluginException pe1) {
- logger.log(Level.WARNING, "Could not load all own identities!", pe1);
+ } catch (WebOfTrustException wote1) {
+ logger.log(Level.WARNING, "Could not load all own identities!", wote1);
return Collections.emptySet();
}
}
//
- // ACTIONS
- //
-
- /**
- * Adds a context to the given own identity.
- *
- * @param ownIdentity
- * The own identity
- * @param context
- * The context to add
- */
- public void addContext(OwnIdentity ownIdentity, String context) {
- if (ownIdentity.hasContext(context)) {
- return;
- }
- try {
- webOfTrustConnector.addContext(ownIdentity, context);
- ownIdentity.addContext(context);
- } catch (PluginException pe1) {
- logger.log(Level.WARNING, "Could not add context " + context + " to OwnIdentity " + ownIdentity + ".", pe1);
- }
- }
-
- /**
- * Removes a context from the given own identity.
- *
- * @param ownIdentity
- * The own identity
- * @param context
- * The context to remove
- */
- public void removeContext(OwnIdentity ownIdentity, String context) {
- if (!ownIdentity.hasContext(context)) {
- return;
- }
- try {
- webOfTrustConnector.removeContext(ownIdentity, context);
- ownIdentity.removeContext(context);
- } catch (PluginException pe1) {
- logger.log(Level.WARNING, "Could not remove context " + context + " from OwnIdentity " + ownIdentity + ".", pe1);
- }
- }
-
- /**
- * Sets the property with the given name to the given value.
- *
- * @param ownIdentity
- * The own identity
- * @param name
- * The name of the property
- * @param value
- * The value of the property
- */
- public void setProperty(OwnIdentity ownIdentity, String name, String value) {
- try {
- webOfTrustConnector.setProperty(ownIdentity, name, value);
- ownIdentity.setProperty(name, value);
- } catch (PluginException pe1) {
- logger.log(Level.WARNING, "Could not set property “" + name + "” to “" + value + "” for OwnIdentity: " + ownIdentity, pe1);
- }
- }
-
- /**
- * Removes the property with the given name.
- *
- * @param ownIdentity
- * The own identity
- * @param name
- * The name of the property to remove
- */
- public void removeProperty(OwnIdentity ownIdentity, String name) {
- try {
- webOfTrustConnector.removeProperty(ownIdentity, name);
- ownIdentity.removeProperty(name);
- } catch (PluginException pe1) {
- logger.log(Level.WARNING, "Could not remove property “" + name + "” from OwnIdentity: " + ownIdentity, pe1);
- }
- }
-
- //
// SERVICE METHODS
//
*/
@Override
protected void serviceRun() {
- Map<String, Identity> oldIdentities = Collections.emptyMap();
+ Map<OwnIdentity, Map<String, Identity>> oldIdentities = Collections.emptyMap();
while (!shouldStop()) {
- Map<String, Identity> currentIdentities = new HashMap<String, Identity>();
+ Map<OwnIdentity, Map<String, Identity>> currentIdentities = new HashMap<OwnIdentity, Map<String, Identity>>();
Map<String, OwnIdentity> currentOwnIdentities = new HashMap<String, OwnIdentity>();
- /* get all identities with the wanted context from WoT. */
- Set<OwnIdentity> ownIdentities;
try {
- ownIdentities = webOfTrustConnector.loadAllOwnIdentities();
+ /* get all identities with the wanted context from WoT. */
+ Set<OwnIdentity> ownIdentities = webOfTrustConnector.loadAllOwnIdentities();
/* check for changes. */
for (OwnIdentity ownIdentity : ownIdentities) {
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<Identity> trustedIdentities = webOfTrustConnector.loadTrustedIdentities(ownIdentity, context);
+ Map<String, Identity> identities = new HashMap<String, Identity>();
+ 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<String> oldContexts = oldIdentity.getContexts();
- Set<String> 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<String, String> oldProperties = oldIdentity.getProperties();
- Map<String, String> newProperties = newIdentity.getProperties();
- if (oldProperties.size() != newProperties.size()) {
- identityListenerManager.fireIdentityUpdated(newIdentity);
- continue;
- }
- for (Entry<String, String> 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<String> oldContexts = oldIdentity.getContexts();
+ Set<String> 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<String, String> oldProperties = oldIdentity.getProperties();
+ Map<String, String> newProperties = newIdentity.getProperties();
+ if (oldProperties.size() != newProperties.size()) {
+ identityListenerManager.fireIdentityUpdated(ownIdentity, newIdentity);
+ continue;
+ }
+ for (Entry<String, String> 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. */
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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-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.
+ * <p>
+ * 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.
+ * <p>
+ * 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<String> contexts) throws WebOfTrustException;
- //
- // ACCESSORS
- //
+ /**
+ * Removes the given context from this identity.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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<String, String> properties) throws WebOfTrustException;
+
+ /**
+ * Removes the property with the given name.
+ * <p>
+ * 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;
}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class PluginConnector implements FredPluginTalker {
-
- /** The plugin respirator. */
- private final PluginRespirator pluginRespirator;
-
- /** Connector listener managers for all plugin connections. */
- private final Map<Pair<String, String>, ConnectorListenerManager> connectorListenerManagers = Collections.synchronizedMap(new HashMap<Pair<String, String>, 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<String, String>(pluginName, identifier));
- if (create && (connectorListenerManager == null)) {
- connectorListenerManager = new ConnectorListenerManager(this);
- connectorListenerManagers.put(new Pair<String, String>(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);
- }
-
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet.wot;
-
-/**
- * Exception that signals an error when communicating with a plugin.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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);
- }
-
-}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot;
+
+/**
+ * Container class for trust in the web of trust.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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;
+ }
+
+}
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.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;
/** 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<String, Reply> replies = Collections.synchronizedMap(new HashMap<String, Reply>());
+ /** The current reply. */
+ private Reply reply;
/** The plugin connector. */
private final PluginConnector pluginConnector;
* 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<OwnIdentity> loadAllOwnIdentities() throws PluginException {
- Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetOwnIdentities").get(), "OwnIdentities");
+ public Set<OwnIdentity> loadAllOwnIdentities() throws WebOfTrustException {
+ Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetOwnIdentities").get());
SimpleFieldSet fields = reply.getFields();
int ownIdentityCounter = -1;
Set<OwnIdentity> ownIdentities = new HashSet<OwnIdentity>();
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;
* if an error occured talking to the Web of Trust plugin
*/
public Set<Identity> 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<Identity> identities = new HashSet<Identity>();
int identityCounter = -1;
}
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;
* 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());
}
/**
* 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());
}
/**
* 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");
}
* 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());
}
/**
* 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());
}
/**
* 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());
}
//
*
* @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);
}
/**
* 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;
}
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);
/** The payload of the reply. */
private Bucket data;
+ /** Empty constructor. */
+ public Reply() {
+ /* do nothing. */
+ }
+
/**
* Returns the fields of the reply.
*
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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);
+ }
+
+}
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;
}
/** 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);
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class JavascriptFilter implements Filter {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Object format(DataProvider dataProvider, Object data, Map<String, String> 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();
+ }
+
+}
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;
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);
}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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:
+ * <dl>
+ * <dt>assigned</dt>
+ * <dd>{@link Boolean} that indicates whether this trust relationship has an
+ * explicit value assigned to it.</dd>
+ * </dl>
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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);
+ }
+
+}
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);
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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);
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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);
+ }
+
+}
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;
Integer birthDay = profile.getBirthDay();
Integer birthMonth = profile.getBirthMonth();
Integer birthYear = profile.getBirthYear();
+ List<Field> 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);
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;
+ }
}
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));
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());
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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);
+ }
+ }
+
+}
@Override
protected void postProcess(Request request, DataProvider dataProvider) {
Sone sone = (Sone) dataProvider.get("sone");
+ if (sone == null) {
+ return;
+ }
List<Post> posts = sone.getPosts();
for (Post post : posts) {
webInterface.getCore().markPostKnown(post);
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;
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;
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;
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());
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));
* 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"));
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"));
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")));
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);
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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()));
+ }
+
+}
return createSuccessJsonObject();
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected boolean requiresLogin() {
+ return false;
+ }
+
}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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());
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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();
+ }
+
+}
return false;
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected boolean requiresLogin() {
+ return false;
+ }
+
//
// PRIVATE METHODS
//
return false;
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected boolean requiresLogin() {
+ return false;
+ }
+
}
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
//
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));
}
return createSuccessJsonObject();
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected boolean requiresLogin() {
+ return false;
+ }
+
}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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();
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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());
+ }
+
+}
return createSuccessJsonObject();
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected boolean requiresLogin() {
+ return false;
+ }
+
}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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());
+ }
+
+}
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
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
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}
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!
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.
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…
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!
/* 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;
}
outline: none;
}
+input[type=text].short {
+ width: 25em;
+}
+
textarea {
height: 4em;
}
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;
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;
color: rgb(255, 172, 0);
}
-#sone .post .show-reply-form:before {
- content: ' ‧ ';
-}
-
#sone .post .create-reply {
clear: both;
background-color: #f0f0ff;
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 {
}
#sone .confirm {
- font-weight: bold !important;
- color: red !important;
+ font-weight: bold;
+ color: red;
}
textarea.show();
}
$(inputField.get(0).form).submit(function() {
+ inputField.attr("disabled", "disabled");
if (!optional && (textarea.val() == "")) {
return false;
}
return;
}
commentElement = (function(postId) {
+ separator = $("<span> · </span>").addClass("separator");
var commentElement = $("<div><span>Comment</span></div>").addClass("show-reply-form").click(function() {
markPostAsKnown(getPostElement(this));
replyElement = $("#sone .post#" + postId + " .create-reply");
return commentElement;
})(postId);
$(insertAfterThisElement).after(commentElement.clone(true));
+ $(insertAfterThisElement).after(separator);
}
var translations = {};
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");
}
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) {
});
}
+/**
+ * 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) {
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"));
});
/* mark everything as known on click. */
- $(postElement).click(function() {
+ $(postElement).click(function(event) {
+ if ($(event.target).hasClass("click-to-show")) {
+ return false;
+ }
markPostAsKnown(this);
});
})(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));
(function(postElement) {
$.getJSON("markPostAsKnown.ajax", {"formPassword": getFormPassword(), "post": getPostId(postElement)}, function(data, textStatus) {
$(postElement).removeClass("new");
+ $(".click-to-show", postElement).removeClass("new");
});
})(postElement);
}
$("#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
//
--- /dev/null
+<%include include/head.html>
+
+ <h1><%= Page.DeleteProfileField.Page.Title|l10n|html></h1>
+
+ <p><%= Page.DeleteProfileField.Text|l10n|html></p>
+
+ <div class="profile-field">
+ <div class="name"><% field.name|html></div>
+ <div class="value"><% field.value|html></div>
+ </div>
+
+ <form id="delete-profile-field" method="post">
+ <input type="hidden" name="formPassword" value="<% formPassword|html>" />
+ <input type="hidden" name="field" value="<% field.id|html>" />
+ <button type="submit" name="confirm" value="true"><%= Page.DeleteProfileField.Button.Yes|l10n|html></button>
+ <button type="submit" name="cancel" value="true"><%= Page.DeleteProfileField.Button.No|l10n|html></button>
+ </form>
+
+<%include include/tail.html>
+++ /dev/null
-<%include include/head.html>
-
-<%include include/tail.html>
<%include include/head.html>
<script language="javascript">
- $(document).ready(function() {
+ function recheckMoveButtons() {
+ $("#sone .profile-field").each(function() {
+ $(".move-up-field", this).toggleClass("hidden", $(this).prev(".profile-field").length == 0);
+ $(".move-down-field", this).toggleClass("hidden", $(this).next(".profile-field").length == 0);
+ });
+ }
+
+ $(function() {
getTranslation("WebInterface.DefaultText.FirstName", function(firstNameDefaultText) {
registerInputTextareaSwap("#sone #edit-profile input[name=first-name]", firstNameDefaultText, "first-name", true, true);
});
getTranslation("WebInterface.DefaultText.BirthYear", function(birthYearDefaultText) {
registerInputTextareaSwap("#sone #edit-profile input[name=birth-year]", birthYearDefaultText, "birth-year", true, true);
});
+ getTranslation("WebInterface.DefaultText.FieldName", function(fieldNameDefaultText) {
+ registerInputTextareaSwap("#sone #add-profile-field input[name=field-name]", fieldNameDefaultText, "field-name", true, true);
+ });
+
+ <%foreach fields field>
+ registerInputTextareaSwap("#sone #edit-profile input[name=field-<% loop.count>]", <% field.key|js>, "field-<% loop.count>", true, true);
+ <%/foreach>
/* hide all the labels. */
- $("#sone #edit-profile label").hide();
+ $("#sone #edit-profile label, #sone #add-profile-field label").hide();
+
+ /* ajaxify the delete buttons. */
+ getTranslation("Page.EditProfile.Fields.Button.ReallyDelete", function(reallyDeleteText) {
+ $("#sone #edit-profile .delete-field-name button").each(function() {
+ confirmButton = $(this).clone().addClass("hidden").addClass("confirm").text(reallyDeleteText).insertAfter(this);
+ (function(deleteButton, confirmButton) {
+ deleteButton.click(function() {
+ deleteButton.fadeOut("slow", function() {
+ confirmButton.fadeIn("slow");
+ $(document).one("click", function() {
+ if (this != confirmButton.get(0)) {
+ confirmButton.fadeOut("slow", function() {
+ deleteButton.fadeIn("slow");
+ });
+ }
+ return false;
+ });
+ });
+ return false;
+ });
+ confirmButton.click(function() {
+ confirmButton.fadeOut("slow");
+ buttonName = confirmButton.attr("name");
+ fieldId = buttonName.substring("delete-field-".length);
+ deleteProfileField(fieldId);
+ recheckMoveButtons();
+ return false;
+ });
+ })($(this), confirmButton);
+ });
+ });
+
+ /* ajaxify the edit button. */
+ $("#sone #edit-profile .edit-field-name button").each(function() {
+ profileField = $(this).parents(".profile-field");
+ fieldNameElement = profileField.find(".name");
+ inputField = $("input[type=text].short", profileField);
+ confirmButton = $("button.confirm", profileField);
+ cancelButton = $("button.cancel", profileField);
+ (function(editButton, inputField, confirmButton, cancelButton, fieldNameElement) {
+ cleanUp = function(editButton, inputField, confirmButton, cancelButton, fieldNameElement) {
+ editButton.removeAttr("disabled");
+ inputField.addClass("hidden");
+ confirmButton.addClass("hidden");
+ cancelButton.addClass("hidden");
+ fieldNameElement.removeClass("hidden");
+ };
+ confirmButton.click(function() {
+ inputField.attr("disabled", "disabled");
+ confirmButton.attr("disabled", "disabled");
+ cancelButton.attr("disabled", "disabled");
+ editProfileField(confirmButton.parents(".profile-field").attr("id"), inputField.val(), function() {
+ fieldNameElement.text(inputField.val());
+ cleanUp(editButton, inputField, confirmButton, cancelButton, fieldNameElement);
+ });
+ return false;
+ });
+ cancelButton.click(function() {
+ cleanUp(editButton, inputField, confirmButton, cancelButton, fieldNameElement);
+ return false;
+ });
+ inputField.keypress(function(event) {
+ if (event.which == 13) {
+ confirmButton.click();
+ return false;
+ } else if (event.which == 27) {
+ cancelButton.click();
+ return false;
+ }
+ });
+ editButton.click(function() {
+ editButton.attr("disabled", "disabled");
+ fieldNameElement.addClass("hidden");
+ inputField.removeAttr("disabled").val(fieldNameElement.text()).removeClass("hidden").focus().select();
+ confirmButton.removeAttr("disabled").removeClass("hidden");
+ cancelButton.removeAttr("disabled").removeClass("hidden");
+ return false;
+ });
+ })($(this), inputField, confirmButton, cancelButton, fieldNameElement);
+ });
+
+ /* ajaxify “move up” and “move down” buttons. */
+ $("#sone .profile-field .move-down-field button").click(function() {
+ profileField = $(this).parents(".profile-field");
+ moveProfileFieldDown(profileField.attr("id"), function() {
+ next = profileField.next();
+ current = profileField.insertAfter(next);
+ recheckMoveButtons();
+ });
+ return false;
+ });
+ $("#sone .profile-field .move-up-field button").click(function() {
+ profileField = $(this).parents(".profile-field");
+ moveProfileFieldUp(profileField.attr("id"), function() {
+ previous = profileField.prev();
+ current = profileField.insertBefore(previous);
+ recheckMoveButtons();
+ });
+ return false;
+ });
});
</script>
<p><%= Page.EditProfile.Page.Description|l10n|html></p>
<p><%= Page.EditProfile.Page.Hint.Optionality|l10n|html></p>
- <%if changed>
- <p><%= Page.EditProfile.Page.Status.Changed|l10n|html></p>
- <%/if>
-
<form id="edit-profile" method="post">
<input type="hidden" name="formPassword" value="<% formPassword|html>" />
</div>
<div>
- <button type="submit"><%= Page.EditProfile.Button.Save|l10n|html></button>
+ <button type="submit" name="save-profile" value="true"><%= Page.EditProfile.Button.Save|l10n|html></button>
+ </div>
+
+ <h1><%= Page.EditProfile.Fields.Title|l10n|html></h1>
+
+ <p><%= Page.EditProfile.Fields.Description|l10n|html></p>
+
+ <%foreach fields field fieldLoop>
+ <div class="profile-field" id="<% field.id|html>">
+ <div class="name"><% field.name|html></div>
+ <input class="short hidden" type="text"><button class="confirm hidden" type="button">✔</button><button class="cancel hidden" type="button">✘</button>
+ <div class="edit-field-name"><button type="submit" name="edit-field-<% field.id|html>" value="true"><%= Page.EditProfile.Fields.Button.Edit|l10n|html></button></div>
+ <div class="delete-field-name"><button type="submit" name="delete-field-<% field.id|html>" value="true"><%= Page.EditProfile.Fields.Button.Delete|l10n|html></button></div>
+ <div class="<%if fieldLoop.last>hidden <%/if>move-down-field"><button type="submit" name="move-down-field-<% field.id|html>" value="true"><%= Page.EditProfile.Fields.Button.MoveDown|l10n|html></button></div>
+ <div class="<%if fieldLoop.first>hidden <%/if>move-up-field"><button type="submit" name="move-up-field-<% field.id|html>" value="true"><%= Page.EditProfile.Fields.Button.MoveUp|l10n|html></button></div>
+ <div class="value"><input type="text" name="field-<% field.id|html>" value="<% field.value|html>" /></div>
+ </div>
+
+ <%if fieldLoop.last>
+ <div>
+ <button type="submit" name="save-profile" value="true"><%= Page.EditProfile.Button.Save|l10n|html></button>
+ </div>
+ <%/if>
+ <%/foreach>
+
+ </form>
+
+ <form id="add-profile-field" method="post">
+ <input type="hidden" name="formPassword" value="<% formPassword|html>" />
+
+ <a name="profile-fields"></a>
+ <h2><%= Page.EditProfile.Fields.AddField.Title|l10n|html></h2>
+
+ <%if duplicateFieldName>
+ <p><%= Page.EditProfile.Error.DuplicateFieldName|l10n|replace needle="{fieldName}" replacementKey="fieldName"|html></p>
+ <%/if>
+
+ <div id="new-field">
+ <label for="new-field"><%= Page.EditProfile.Fields.AddField.Label.Name|l10n|html></label>
+ <input type="text" name="field-name" value="" />
+ <button type="submit" name="add-field" value="true"><%= Page.EditProfile.Fields.AddField.Button.AddField|l10n|html></button>
</div>
</form>
--- /dev/null
+<%include include/head.html>
+
+ <h1><%= Page.EditProfileField.Page.Title|l10n|html></h1>
+
+ <p><%= Page.EditProfileField.Text|l10n|html></p>
+
+ <%if duplicateFieldName>
+ <p><%= Page.EditProfileField.Error.DuplicateFieldName|l10n|html></p>
+ <%/if>
+
+ <form method="post">
+ <input type="hidden" name="formPassword" value="<% formPassword|html>" />
+ <input type="hidden" name="field" value="<% field.id|html>" />
+ <div>
+ <input type="text" name="name" value="<% field.name|html>" />
+ <button type="submit" name="save" value="true"><%= Page.EditProfileField.Button.Save|l10n|html></button>
+ </div>
+ <p>
+ <button type="reset"><%= Page.EditProfileField.Button.Reset|l10n|html></button>
+ <button type="submit" name="cancel" value="true"><%= Page.EditProfileField.Button.Cancel|l10n|html></button>
+ </p>
+ </form>
+
+<%include include/tail.html>
+++ /dev/null
-<%include include/head.html>
-
-<%include include/tail.html>
<a name="post-<% post.id|html>"></a>
<div id="<% post.id|html>" class="post<%if loop.last> last<%/if><%if post.new> new<%/if>">
<div class="post-time hidden"><% post.time|html></div>
+ <div class="post-author hidden"><% post.sone.id|html></div>
<div class="avatar">
<img src="/WoT/GetIdenticon?identity=<% post.sone.id|html>&width=48&height=48" width="48" height="48" alt="Avatar Image" />
</div>
</div>
<div class="post-status-line status-line">
<div class="time"><a href="viewPost.html?post=<% post.id|html>"><% post.time|date format="MMM d, yyyy, HH:mm:ss"></a></div>
+ <span class='separator'>·</span>
<div class="likes<%if post.likes.size|match value=0> hidden<%/if>"><span title="<% post.likes.soneNames|html>">↑<span class="like-count"><% post.likes.size></span></span></div>
<%ifnull ! currentSone>
<form class="like like-post<%if post.liked> hidden<%/if>" action="like.html" method="post">
<button type="submit" value="1"><%= View.Post.UnlikeLink|l10n|html></button>
</form>
<%/if>
+ <%if !post.sone.current>
+ <span class='separator'>·</span>
+ <form class="trust post-trust<%if post.sone.trust.assigned> hidden<%/if>" action="trust.html" method="post">
+ <input type="hidden" name="formPassword" value="<% formPassword|html>" />
+ <input type="hidden" name="returnPage" value="<% request.uri|html>" />
+ <input type="hidden" name="sone" value="<% post.sone.id|html>" />
+ <button type="submit" title="<%= View.Trust.Tooltip.Trust|l10n|html>">✓</button>
+ </form>
+ <form class="distrust post-distrust" action="distrust.html" method="post">
+ <input type="hidden" name="formPassword" value="<% formPassword|html>" />
+ <input type="hidden" name="returnPage" value="<% request.uri|html>" />
+ <input type="hidden" name="sone" value="<% post.sone.id|html>" />
+ <button type="submit" title="<%= View.Trust.Tooltip.Distrust|l10n|html>">✗</button>
+ </form>
+ <form class="untrust post-untrust<%if !post.sone.trust.assigned> hidden<%/if>" action="untrust.html" method="post">
+ <input type="hidden" name="formPassword" value="<% formPassword|html>" />
+ <input type="hidden" name="returnPage" value="<% request.uri|html>" />
+ <input type="hidden" name="sone" value="<% post.sone.id|html>" />
+ <button type="submit" title="<%= View.Trust.Tooltip.Untrust|l10n|html>">↶</button>
+ </form>
+ <%/if>
<%if post.sone.current>
+ <span class='separator'>·</span>
<form class="delete delete-post" action="deletePost.html" method="post">
<input type="hidden" name="formPassword" value="<% formPassword|html>" />
<input type="hidden" name="returnPage" value="<% request.uri|html>" />
<a name="reply-<% reply.id|html>"></a>
<div id="<% reply.id|html>" class="reply<%if reply.new> new<%/if>">
<div class="reply-time hidden"><% reply.time|html></div>
+ <div class="reply-author hidden"><% reply.sone.id|html></div>
<div class="avatar">
<img src="/WoT/GetIdenticon?identity=<% reply.sone.id|html>&width=36&height=36" width="36" height="36" alt="Avatar Image" />
</div>
</div>
<div class="reply-status-line status-line">
<div class="time"><% reply.time|date format="MMM d, yyyy, HH:mm:ss"></div>
+ <span class='separator'>·</span>
<div class="likes<%if reply.likes.size|match value=0> hidden<%/if>"><span title="<% reply.likes.soneNames|html>">↑<span class="like-count"><% reply.likes.size></span></span></div>
<%ifnull ! currentSone>
<form class="like like-reply<%if reply.liked> hidden<%/if>" action="like.html" method="post">
<button type="submit" value="1"><%= View.Post.UnlikeLink|l10n|html></button>
</form>
<%/if>
+ <%if !reply.sone.current>
+ <span class='separator'>·</span>
+ <form class="trust reply-trust<%if reply.sone.trust.assigned> hidden<%/if>" action="trust.html" method="post">
+ <input type="hidden" name="formPassword" value="<% formPassword|html>" />
+ <input type="hidden" name="returnPage" value="<% request.uri|html>" />
+ <input type="hidden" name="sone" value="<% reply.sone.id|html>" />
+ <button type="submit" title="<%= View.Trust.Tooltip.Trust|l10n|html>">✓</button>
+ </form>
+ <form class="distrust reply-distrust" action="distrust.html" method="post">
+ <input type="hidden" name="formPassword" value="<% formPassword|html>" />
+ <input type="hidden" name="returnPage" value="<% request.uri|html>" />
+ <input type="hidden" name="sone" value="<% reply.sone.id|html>" />
+ <button type="submit" title="<%= View.Trust.Tooltip.Distrust|l10n|html>">✗</button>
+ </form>
+ <form class="untrust reply-untrust<%if !reply.sone.trust.assigned> hidden<%/if>" action="untrust.html" method="post">
+ <input type="hidden" name="formPassword" value="<% formPassword|html>" />
+ <input type="hidden" name="returnPage" value="<% request.uri|html>" />
+ <input type="hidden" name="sone" value="<% reply.sone.id|html>" />
+ <button type="submit" title="<%= View.Trust.Tooltip.Untrust|l10n|html>">↶</button>
+ </form>
+ <%/if>
<%if reply.sone.current>
+ <span class='separator'>·</span>
<form class="delete delete-reply" action="deleteReply.html" method="post">
<input type="hidden" name="formPassword" value="<% formPassword|html>" />
<input type="hidden" name="returnPage" value="<% request.uri|html>" />
<birth-day><% currentSone.profile.birthDay|xml></birth-day>
<birth-month><% currentSone.profile.birthMonth|xml></birth-month>
<birth-year><% currentSone.profile.birthYear|xml></birth-year>
+ <fields>
+ <%foreach currentSone.profile.fields field>
+ <field>
+ <field-name><% field.key|xml></field-name>
+ <field-value><% field.value|xml></field-value>
+ </field>
+ <%/foreach>
+ </fields>
</profile>
<posts>
--- /dev/null
+<%include include/head.html>
+
+ <h1><%= Page.Invalid.Page.Title|l10n|html></h1>
+
+ <p><%= Page.Invalid.Text|l10n|html|replace needle="{link}" replacement='<a href="index.html">'|replace needle="{/link}" replacement='</a>'></p>
+
+<%include include/tail.html>
+++ /dev/null
-<%include include/head.html>
-
-<%include include/tail.html>
+++ /dev/null
-<%include include/head.html>
-
-<%include include/tail.html>
+++ /dev/null
-<%include include/head.html>
-
-<%include include/tail.html>
<p><%= Page.Options.Option.InsertionDelay.Description|l10n|html></p>
<p><input type="text" name="insertion-delay" value="<% insertion-delay|html>" /></p>
+ <h2><%= Page.Options.Section.TrustOptions.Title|l10n|html></h2>
+
+ <p><%= Page.Options.Option.PositiveTrust.Description|l10n|html></p>
+ <p><input type="text" name="positive-trust" value="<% positive-trust|html>" /></p>
+
+ <p><%= Page.Options.Option.NegativeTrust.Description|l10n|html></p>
+ <p><input type="text" name="negative-trust" value="<% negative-trust|html>" /></p>
+
+ <p><%= Page.Options.Option.TrustComment.Description|l10n|html></p>
+ <p><input type="text" name="trust-comment" value="<% trust-comment|html>" /></p>
+
<h2><%= Page.Options.Section.RescueOptions.Title|l10n|html></h2>
<p><%= Page.Options.Option.SoneRescueMode.Description|l10n|html></p>
+++ /dev/null
-<%include include/head.html>
-
-<%include include/tail.html>
+++ /dev/null
-<%include include/head.html>
-
-<%include include/tail.html>
+++ /dev/null
-<%include include/head.html>
-
-<%include include/tail.html>
<%if ! sone.current>
<%include include/viewSone.html>
+ <%/if>
+
+ <h1><%= Page.ViewSone.Profile.Title|l10n|html></h1>
+ <div class="profile-field">
+ <div class="name"><%= Page.ViewSone.Profile.Label.Name|l10n|html></div>
+ <div class="value"><a href="/WoT/ShowIdentity?id=<% sone.id|html>"><% sone.niceName|html></a></div>
+ </div>
+
+ <%foreach sone.profile.fields field>
+ <div class="profile-field">
+ <div class="name"><% field.name|html></div>
+ <div class="value"><% field.value|html></div>
+ </div>
+ <%/foreach>
+
+ <%if ! sone.current>
<p><%= Page.ViewSone.WriteAMessage|l10n|html></p>
<form action="createPost.html" id="post-message" method="post">
</form>
<%/if>
-
<h1><%= Page.ViewSone.PostList.Title|l10n|insert needle="{sone}" key=sone.niceName|html></h1>
<div id="posts">