From: David ‘Bombe’ Roden Date: Fri, 14 Jan 2011 07:34:38 +0000 (+0100) Subject: Merge branch 'next' into profile-fields X-Git-Tag: 0.4^2~9^2~32 X-Git-Url: https://git.pterodactylus.net/?p=Sone.git;a=commitdiff_plain;h=bc29afc8fdd96b8c80c767148a917034c124d93e;hp=a6ab825ac08bb90c01055b76b7310b181cb19cec Merge branch 'next' into profile-fields --- diff --git a/src/main/java/net/pterodactylus/sone/core/Core.java b/src/main/java/net/pterodactylus/sone/core/Core.java index 78df9ef..7cbce1b 100644 --- a/src/main/java/net/pterodactylus/sone/core/Core.java +++ b/src/main/java/net/pterodactylus/sone/core/Core.java @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -1033,6 +1034,22 @@ public class Core implements IdentityListener, UpdateListener { profile.setBirthMonth(configuration.getIntValue(sonePrefix + "/Profile/BirthMonth").getValue(null)); profile.setBirthYear(configuration.getIntValue(sonePrefix + "/Profile/BirthYear").getValue(null)); + /* load profile fields. */ + while (true) { + String fieldPrefix = sonePrefix + "/Profile/Fields/" + profile.getFieldNames().size(); + String fieldName = configuration.getStringValue(fieldPrefix + "/Name").getValue(null); + if (fieldName == null) { + break; + } + String fieldValue = configuration.getStringValue(fieldPrefix + "/Value").getValue(null); + if (fieldValue == null) { + logger.log(Level.WARNING, "Invalid profile field found, aborting load!"); + return; + } + profile.addField(fieldName); + profile.setField(fieldName, fieldValue); + } + /* load posts. */ Set posts = new HashSet(); while (true) { @@ -1165,6 +1182,15 @@ public class Core implements IdentityListener, UpdateListener { configuration.getIntValue(sonePrefix + "/Profile/BirthMonth").setValue(profile.getBirthMonth()); configuration.getIntValue(sonePrefix + "/Profile/BirthYear").setValue(profile.getBirthYear()); + /* save profile fields. */ + int fieldCounter = 0; + for (Entry profileField : profile.getFields().entrySet()) { + String fieldPrefix = sonePrefix + "/Profile/Fields/" + fieldCounter++; + configuration.getStringValue(fieldPrefix + "/Name").setValue(profileField.getKey()); + 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()) { diff --git a/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java b/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java index 4328f02..9b80438 100644 --- a/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java +++ b/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java @@ -310,6 +310,26 @@ public class SoneDownloader extends AbstractService { Profile profile = new Profile().setFirstName(profileFirstName).setMiddleName(profileMiddleName).setLastName(profileLastName); profile.setBirthDay(profileBirthDay).setBirthMonth(profileBirthMonth).setBirthYear(profileBirthYear); + /* parse profile fields. */ + SimpleXML profileFieldsXml = profileXml.getNode("fields"); + if (profileFieldsXml != null) { + for (SimpleXML fieldXml : profileFieldsXml.getNodes("field")) { + String fieldName = fieldXml.getValue("field-name", null); + String fieldValue = fieldXml.getValue("field-value", null); + if ((fieldName == null) || (fieldValue == null)) { + logger.log(Level.WARNING, "Downloaded profile field for Sone %s with missing data! Name: %s, Value: %s", new Object[] { sone, fieldName, fieldValue }); + return null; + } + try { + profile.addField(fieldName); + } catch (IllegalArgumentException iae1) { + logger.log(Level.WARNING, "Duplicate field: " + fieldName, iae1); + return null; + } + profile.setField(fieldName, fieldValue); + } + } + /* parse posts. */ SimpleXML postsXml = soneXml.getNode("posts"); Set posts = new HashSet(); diff --git a/src/main/java/net/pterodactylus/sone/data/Fingerprintable.java b/src/main/java/net/pterodactylus/sone/data/Fingerprintable.java new file mode 100644 index 0000000..013e0b3 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/data/Fingerprintable.java @@ -0,0 +1,36 @@ +/* + * Sone - Fingerprintable.java - Copyright © 2011 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.data; + +/** + * Interface for objects that can create a fingerprint of themselves, e.g. to + * detect modifications. The fingerprint should only contain original + * information; derived information should not be included. + * + * @author David ‘Bombe’ Roden + */ +public interface Fingerprintable { + + /** + * Returns the fingerprint of this object. + * + * @return The fingerprint of this object + */ + public String getFingerprint(); + +} diff --git a/src/main/java/net/pterodactylus/sone/data/Profile.java b/src/main/java/net/pterodactylus/sone/data/Profile.java index 7c29430..d0a659c 100644 --- a/src/main/java/net/pterodactylus/sone/data/Profile.java +++ b/src/main/java/net/pterodactylus/sone/data/Profile.java @@ -17,16 +17,22 @@ package net.pterodactylus.sone.data; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import net.pterodactylus.util.validation.Validation; + /** * A profile stores personal information about a {@link Sone}. All information * is optional and can be {@code null}. * * @author David ‘Bombe’ Roden */ -public class Profile { - - /** Whether the profile was modified. */ - private volatile boolean modified; +public class Profile implements Fingerprintable { /** The first name. */ private volatile String firstName; @@ -46,6 +52,12 @@ public class Profile { /** The year of the birth date. */ private volatile Integer birthYear; + /** Additional fields in the profile. */ + private final List fields = Collections.synchronizedList(new ArrayList()); + + /** The field values. */ + private final Map fieldValues = Collections.synchronizedMap(new HashMap()); + /** * Creates a new empty profile. */ @@ -69,6 +81,8 @@ public class Profile { this.birthDay = profile.birthDay; this.birthMonth = profile.birthMonth; this.birthYear = profile.birthYear; + this.fields.addAll(profile.fields); + this.fieldValues.putAll(profile.fieldValues); } // @@ -76,18 +90,6 @@ public class Profile { // /** - * Returns whether this profile was modified after creation. To clear the - * “is modified” flag you need to create a new profile from this one using - * the {@link #Profile(Profile)} constructor. - * - * @return {@code true} if this profile was modified after creation, - * {@code false} otherwise - */ - public boolean isModified() { - return modified; - } - - /** * Returns the first name. * * @return The first name @@ -104,7 +106,6 @@ public class Profile { * @return This profile (for method chaining) */ public Profile setFirstName(String firstName) { - modified |= ((firstName != null) && (!firstName.equals(this.firstName))) || (this.firstName != null); this.firstName = firstName; return this; } @@ -126,7 +127,6 @@ public class Profile { * @return This profile (for method chaining) */ public Profile setMiddleName(String middleName) { - modified |= ((middleName != null) && (!middleName.equals(this.middleName))) || (this.middleName != null); this.middleName = middleName; return this; } @@ -148,7 +148,6 @@ public class Profile { * @return This profile (for method chaining) */ public Profile setLastName(String lastName) { - modified |= ((lastName != null) && (!lastName.equals(this.lastName))) || (this.lastName != null); this.lastName = lastName; return this; } @@ -170,7 +169,6 @@ public class Profile { * @return This profile (for method chaining) */ public Profile setBirthDay(Integer birthDay) { - modified |= ((birthDay != null) && (!birthDay.equals(this.birthDay))) || (this.birthDay != null); this.birthDay = birthDay; return this; } @@ -192,7 +190,6 @@ public class Profile { * @return This profile (for method chaining) */ public Profile setBirthMonth(Integer birthMonth) { - modified |= ((birthMonth != null) && (!birthMonth.equals(this.birthMonth))) || (this.birthMonth != null); this.birthMonth = birthMonth; return this; } @@ -214,9 +211,213 @@ public class Profile { * @return This profile (for method chaining) */ public Profile setBirthYear(Integer birthYear) { - modified |= ((birthYear != null) && (!birthYear.equals(this.birthYear))) || (this.birthYear != null); this.birthYear = birthYear; return this; } + /** + * Appends a new field to the list of fields. + * + * @param field + * The field to add + * @throws IllegalArgumentException + * if the name is not valid + */ + public void addField(String field) throws IllegalArgumentException { + Validation.begin().isNotNull("Field Name", field).check().isGreater("Field Name Length", field.length(), 0).isEqual("Field Name Unique", !fields.contains(field), true).check(); + fields.add(field); + } + + /** + * Moves the field with the given index 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 fieldIndex + * The index of the field to move + */ + public void moveFieldUp(int fieldIndex) { + Validation.begin().isGreater("Field Index", fieldIndex, 0).isLess("Field Index", fieldIndex, fields.size()).check(); + String field = fields.remove(fieldIndex); + fields.add(fieldIndex - 1, field); + } + + /** + * Moves the field with the given name up one position in the field list. + * The field must not be the first field (because you obviously can not move + * the first field further up). + * + * @param field + * The name of the field to move + */ + public void moveFieldUp(String field) { + Validation.begin().isNotNull("Field Name", field).check().isGreater("Field Name Length", field.length(), 0).isEqual("Field Name Existing", fields.contains(field), true).check(); + moveFieldUp(getFieldIndex(field)); + } + + /** + * Moves the field with the given index 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 fieldIndex + * The index of the field to move + */ + public void moveFieldDown(int fieldIndex) { + Validation.begin().isGreaterOrEqual("Field Index", fieldIndex, 0).isLess("Field Index", fieldIndex, fields.size() - 1).check(); + String field = fields.remove(fieldIndex); + fields.add(fieldIndex + 1, field); + } + + /** + * Moves the field with the given name down one position in the field list. + * The field must not be the last field (because you obviously can not move + * the last field further down). + * + * @param field + * The name of the field to move + */ + public void moveFieldDown(String field) { + Validation.begin().isNotNull("Field Name", field).check().isGreater("Field Name Length", field.length(), 0).isEqual("Field Name Existing", fields.contains(field), true).check(); + moveFieldDown(getFieldIndex(field)); + } + + /** + * Removes the field at the given index. + * + * @param fieldIndex + * The index of the field to remove + */ + public void removeField(int fieldIndex) { + Validation.begin().isGreaterOrEqual("Field Index", fieldIndex, 0).isLess("Field Index", fieldIndex, fields.size()).check(); + String field = fields.remove(fieldIndex); + fieldValues.remove(field); + } + + /** + * Removes the field with the given name. + * + * @param field + * The name of the field + */ + public void removeField(String field) { + Validation.begin().isNotNull("Field Name", field).check().isGreater("Field Name Length", field.length(), 0).isEqual("Field Name Existing", fields.contains(field), true).check(); + removeField(getFieldIndex(field)); + } + + /** + * Returns the value of the field with the given name. + * + * @param field + * The name of the field + * @return The value of the field, or {@code null} if there is no such field + */ + public String getField(String field) { + return fieldValues.get(field); + } + + /** + * Sets the value of the field with the given name. + * + * @param fieldIndex + * The index of the field + * @param value + * The value of the field + */ + public void setField(int fieldIndex, String value) { + Validation.begin().isGreaterOrEqual("Field Index", fieldIndex, 0).isLess("Field Index", fieldIndex, fields.size()).check(); + setField(fields.get(fieldIndex), value); + } + + /** + * Sets the value of the field with the given name. + * + * @param field + * The name of the field + * @param value + * The value of the field + */ + public void setField(String field, String value) { + Validation.begin().isNotNull("Field Name", field).check().isGreater("Field Name Length", field.length(), 0).isEqual("Field Name Existing", fields.contains(field), true).check(); + fieldValues.put(field, value); + } + + /** + * Returns a list of all fields stored in this profile. + * + * @return The fields of this profile + */ + public List getFieldNames() { + return Collections.unmodifiableList(fields); + } + + /** + * Returns all field names and their values, ordered the same way + * {@link #getFieldNames()} returns the names of the fields. + * + * @return All field names and values + */ + public Map getFields() { + Map fields = new LinkedHashMap(); + for (String field : getFieldNames()) { + fields.put(field, getField(field)); + } + return fields; + } + + // + // 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(String 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 (String field : fields) { + fingerprint.append(field).append('(').append(fieldValues.get(field)).append(')'); + } + fingerprint.append(")"); + fingerprint.append(")"); + + return fingerprint.toString(); + } + } diff --git a/src/main/java/net/pterodactylus/sone/data/Sone.java b/src/main/java/net/pterodactylus/sone/data/Sone.java index 7d4f7ef..03dff75 100644 --- a/src/main/java/net/pterodactylus/sone/data/Sone.java +++ b/src/main/java/net/pterodactylus/sone/data/Sone.java @@ -40,7 +40,7 @@ import freenet.keys.FreenetURI; * * @author David ‘Bombe’ Roden */ -public class Sone { +public class Sone implements Fingerprintable { /** comparator that sorts Sones by their nice name. */ public static final Comparator NICE_NAME_COMPARATOR = new Comparator() { @@ -580,36 +580,17 @@ public class Sone { return this; } + // + // FINGERPRINTABLE METHODS + // + /** - * Returns a fingerprint of this Sone. The fingerprint only depends on data - * that is actually stored when a Sone is inserted. The fingerprint can be - * used to detect changes in Sone data and can also be used to detect if - * previous changes are reverted. - * - * @return The fingerprint of this Sone + * {@inheritDoc} */ + @Override public synchronized String getFingerprint() { StringBuilder fingerprint = new StringBuilder(); - fingerprint.append("Profile("); - if (profile.getFirstName() != null) { - fingerprint.append("FirstName(").append(profile.getFirstName()).append(')'); - } - if (profile.getMiddleName() != null) { - fingerprint.append("MiddleName(").append(profile.getMiddleName()).append(')'); - } - if (profile.getLastName() != null) { - fingerprint.append("LastName(").append(profile.getLastName()).append(')'); - } - if (profile.getBirthDay() != null) { - fingerprint.append("BirthDay(").append(profile.getBirthDay()).append(')'); - } - if (profile.getBirthMonth() != null) { - fingerprint.append("BirthMonth(").append(profile.getBirthMonth()).append(')'); - } - if (profile.getBirthYear() != null) { - fingerprint.append("BirthYear(").append(profile.getBirthYear()).append(')'); - } - fingerprint.append(")"); + fingerprint.append(profile.getFingerprint()); fingerprint.append("Posts("); for (Post post : getPosts()) { diff --git a/src/main/java/net/pterodactylus/sone/web/EditProfilePage.java b/src/main/java/net/pterodactylus/sone/web/EditProfilePage.java index 01d4815..4f7e1f3 100644 --- a/src/main/java/net/pterodactylus/sone/web/EditProfilePage.java +++ b/src/main/java/net/pterodactylus/sone/web/EditProfilePage.java @@ -64,20 +64,20 @@ public class EditProfilePage extends SoneTemplatePage { Integer birthMonth = profile.getBirthMonth(); Integer birthYear = profile.getBirthYear(); 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); currentSone.setProfile(profile); + throw new RedirectException("index.html"); } - throw new RedirectException("index.html"); } dataProvider.set("firstName", firstName); dataProvider.set("middleName", middleName); diff --git a/src/main/resources/i18n/sone.en.properties b/src/main/resources/i18n/sone.en.properties index 740cf45..f372edf 100644 --- a/src/main/resources/i18n/sone.en.properties +++ b/src/main/resources/i18n/sone.en.properties @@ -70,7 +70,6 @@ Page.EditProfile.Birthday.Title=Birthday Page.EditProfile.Birthday.Label.Day=Day: Page.EditProfile.Birthday.Label.Month=Month: Page.EditProfile.Birthday.Label.Year=Year: -Page.EditProfile.Page.Status.Changed=Your changes have been saved and will be inserted shortly. Page.EditProfile.Button.Save=Save Profile Page.CreatePost.Title=Create Post - Sone @@ -92,6 +91,8 @@ Page.ViewSone.UnknownSone.Description=This Sone has not yet been retrieved. Plea Page.ViewSone.WriteAMessage=You can write a message to this Sone here. Please note that everybody will be able to read this message! Page.ViewSone.PostList.Title=Posts by {sone} Page.ViewSone.PostList.Text.NoPostYet=This Sone has not yet posted anything. +Page.ViewSone.Profile.Title=Profile +Page.ViewSone.Profile.Label.Name=Name Page.ViewPost.Title=View Post - Sone Page.ViewPost.Page.Title=View Post by {sone} diff --git a/src/main/resources/static/css/sone.css b/src/main/resources/static/css/sone.css index 4f0c49e..def0be0 100644 --- a/src/main/resources/static/css/sone.css +++ b/src/main/resources/static/css/sone.css @@ -438,10 +438,6 @@ textarea { display: inline; } -#sone #create-sone { - -} - #sone #tail { margin-top: 1em; border-top: solid 1px #ccc; diff --git a/src/main/resources/templates/editProfile.html b/src/main/resources/templates/editProfile.html index d1d5943..918b0c1 100644 --- a/src/main/resources/templates/editProfile.html +++ b/src/main/resources/templates/editProfile.html @@ -31,10 +31,6 @@

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

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

- <%if changed> -

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

- <%/if> -
@@ -71,7 +67,7 @@
- +
diff --git a/src/main/resources/templates/viewSone.html b/src/main/resources/templates/viewSone.html index 884fa0f..1054e01 100644 --- a/src/main/resources/templates/viewSone.html +++ b/src/main/resources/templates/viewSone.html @@ -19,7 +19,23 @@ <%if ! sone.current> <%include include/viewSone.html> + <%/if> + +

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

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

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

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

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