From: David ‘Bombe’ Roden Date: Tue, 22 Mar 2011 18:46:01 +0000 (+0100) Subject: Merge branch 'next' into image-management X-Git-Tag: beta-freefall-0.6.2-1~101 X-Git-Url: https://git.pterodactylus.net/?p=Sone.git;a=commitdiff_plain;h=33f333b35a73d3d4a4e79f41e9dd7b342db87b1a;hp=-c Merge branch 'next' into image-management Conflicts: src/main/java/net/pterodactylus/sone/core/Core.java src/main/java/net/pterodactylus/sone/data/Sone.java src/main/java/net/pterodactylus/sone/web/WebInterface.java src/main/resources/i18n/sone.en.properties --- 33f333b35a73d3d4a4e79f41e9dd7b342db87b1a diff --combined src/main/java/net/pterodactylus/sone/core/Core.java index 6813493,7dc398e..d61211d --- a/src/main/java/net/pterodactylus/sone/core/Core.java +++ b/src/main/java/net/pterodactylus/sone/core/Core.java @@@ -31,22 -31,24 +31,26 @@@ import java.util.logging.Logger import net.pterodactylus.sone.core.Options.DefaultOption; import net.pterodactylus.sone.core.Options.Option; import net.pterodactylus.sone.core.Options.OptionWatcher; +import net.pterodactylus.sone.data.Album; import net.pterodactylus.sone.data.Client; +import net.pterodactylus.sone.data.Image; 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; @@@ -83,6 -85,9 +87,9 @@@ public class Core implements IdentityLi /** The options. */ private final Options options = new Options(); + /** The preferences. */ + private final Preferences preferences = new Preferences(options); + /** The core listener manager. */ private final CoreListenerManager coreListenerManager = new CoreListenerManager(this); @@@ -153,12 -158,13 +160,19 @@@ /** All known replies. */ private Set knownReplies = new HashSet(); + /** All bookmarked posts. */ + /* synchronize access on itself. */ + private Set bookmarkedPosts = new HashSet(); + + /** Trusted identities, sorted by own identities. */ + private Map> trustedIdentities = Collections.synchronizedMap(new HashMap>()); + + /** All known albums. */ + private Map albums = new HashMap(); + + /** All known images. */ + private Map images = new HashMap(); + /** * Creates a new core. * @@@ -222,18 -228,8 +236,8 @@@ * * @return The options of the core */ - public Options getOptions() { - return options; - } - - /** - * Returns whether the “Sone rescue mode” is currently activated. - * - * @return {@code true} if the “Sone rescue mode” is currently activated, - * {@code false} if it is not - */ - public boolean isSoneRescueMode() { - return options.getBooleanOption("SoneRescueMode").get(); + public Preferences getPreferences() { + return preferences; } /** @@@ -416,6 -412,7 +420,7 @@@ if ((sone == null) && create) { sone = new Sone(id); localSones.put(id, sone); + setSoneStatus(sone, SoneStatus.unknown); } return sone; } @@@ -459,6 -456,7 +464,7 @@@ if ((sone == null) && create) { sone = new Sone(id); remoteSones.put(id, sone); + setSoneStatus(sone, SoneStatus.unknown); } return sone; } @@@ -493,22 -491,15 +499,15 @@@ } /** - * Returns whether the given Sone is a new Sone. After this check, the Sone - * is marked as known, i.e. a second call with the same parameters will - * always yield {@code false}. + * Returns whether the Sone with the given ID is a new Sone. * - * @param sone - * The sone to check for + * @param soneId + * The ID of the sone to check for * @return {@code true} if the given Sone is new, false otherwise */ - public boolean isNewSone(Sone sone) { + public boolean isNewSone(String soneId) { synchronized (newSones) { - boolean isNew = !knownSones.contains(sone.getId()) && newSones.remove(sone.getId()); - knownSones.add(sone.getId()); - if (isNew) { - coreListenerManager.fireMarkSoneKnown(sone); - } - return isNew; + return !knownSones.contains(soneId) && newSones.contains(soneId); } } @@@ -525,6 -516,19 +524,19 @@@ } /** + * 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 @@@ -557,8 -561,7 +569,7 @@@ } /** - * Returns whether the given post ID is new. After this method returns it is - * marked a known post ID. + * Returns whether the given post ID is new. * * @param postId * The post ID @@@ -566,32 -569,8 +577,8 @@@ * otherwise */ public boolean isNewPost(String postId) { - return isNewPost(postId, true); - } - - /** - * Returns whether the given post ID is new. If {@code markAsKnown} is - * {@code true} then after this method returns the post ID is marked a known - * post ID. - * - * @param postId - * The post ID - * @param markAsKnown - * {@code true} to mark the post ID as known, {@code false} to - * not to mark it as known - * @return {@code true} if the post is considered to be new, {@code false} - * otherwise - */ - public boolean isNewPost(String postId, boolean markAsKnown) { synchronized (newPosts) { - boolean isNew = !knownPosts.contains(postId) && newPosts.contains(postId); - if (markAsKnown) { - Post post = getPost(postId, false); - if (post != null) { - markPostKnown(post); - } - } - return isNew; + return !knownPosts.contains(postId) && newPosts.contains(postId); } } @@@ -660,30 -639,8 +647,8 @@@ * otherwise */ public boolean isNewReply(String replyId) { - return isNewReply(replyId, true); - } - - /** - * Returns whether the reply with the given ID is new. - * - * @param replyId - * The ID of the reply to check - * @param markAsKnown - * {@code true} to mark the reply as known, {@code false} to not - * to mark it as known - * @return {@code true} if the reply is considered to be new, {@code false} - * otherwise - */ - public boolean isNewReply(String replyId, boolean markAsKnown) { synchronized (newReplies) { - boolean isNew = !knownReplies.contains(replyId) && newReplies.contains(replyId); - if (markAsKnown) { - Reply reply = getReply(replyId, false); - if (reply != null) { - markReplyKnown(reply); - } - } - return isNew; + return !knownReplies.contains(replyId) && newReplies.contains(replyId); } } @@@ -722,74 -679,49 +687,119 @@@ } /** + * Returns whether the given post is bookmarked. + * + * @param post + * The post to check + * @return {@code true} if the given post is bookmarked, {@code false} + * otherwise + */ + public boolean isBookmarked(Post post) { + return isPostBookmarked(post.getId()); + } + + /** + * Returns whether the post with the given ID is bookmarked. + * + * @param id + * The ID of the post to check + * @return {@code true} if the post with the given ID is bookmarked, + * {@code false} otherwise + */ + public boolean isPostBookmarked(String id) { + synchronized (bookmarkedPosts) { + return bookmarkedPosts.contains(id); + } + } + + /** + * Returns all currently known bookmarked posts. + * + * @return All bookmarked posts + */ + public Set getBookmarkedPosts() { + Set posts = new HashSet(); + synchronized (bookmarkedPosts) { + for (String bookmarkedPostId : bookmarkedPosts) { + Post post = getPost(bookmarkedPostId, false); + if (post != null) { + posts.add(post); + } + } + } + return posts; + } + ++ /** ++ * + * Returns the album with the given ID, creating a new album if no album + * with the given ID can be found. + * + * @param albumId + * The ID of the album + * @return The album with the given ID + */ + public Album getAlbum(String albumId) { + return getAlbum(albumId, true); + } + + /** + * Returns the album with the given ID, optionally creating a new album if + * an album with the given ID can not be found. + * + * @param albumId + * The ID of the album + * @param create + * {@code true} to create a new album if none exists for the + * given ID + * @return The album with the given ID, or {@code null} if no album with the + * given ID exists and {@code create} is {@code false} + */ + public Album getAlbum(String albumId, boolean create) { + synchronized (albums) { + Album album = albums.get(albumId); + if (create && (album == null)) { + album = new Album(albumId); + albums.put(albumId, album); + } + return album; + } + } + + /** + * Returns the image with the given ID, creating it if necessary. + * + * @param imageId + * The ID of the image + * @return The image with the given ID + */ + public Image getImage(String imageId) { + return getImage(imageId, true); + } + + /** + * Returns the image with the given ID, optionally creating it if it does + * not exist. + * + * @param imageId + * The ID of the image + * @param create + * {@code true} to create an image if none exists with the given + * ID + * @return The image with the given ID, or {@code null} if none exists and + * none was created + */ + public Image getImage(String imageId, boolean create) { + synchronized (images) { + Image image = images.get(imageId); + if (create && (image == null)) { + image = new Image(imageId); + images.put(imageId, image); + } + return image; + } + } + // // ACTIONS // @@@ -876,7 -808,7 +886,7 @@@ soneInserters.put(sone, soneInserter); setSoneStatus(sone, SoneStatus.idle); loadSone(sone); - if (!isSoneRescueMode()) { + if (!preferences.isSoneRescueMode()) { soneInserter.start(); } new Thread(new Runnable() { @@@ -884,7 -816,7 +894,7 @@@ @Override @SuppressWarnings("synthetic-access") public void run() { - if (!isSoneRescueMode()) { + if (!preferences.isSoneRescueMode()) { soneDownloader.fetchSone(sone); return; } @@@ -892,7 -824,7 +902,7 @@@ coreListenerManager.fireRescuingSone(sone); lockSone(sone); long edition = sone.getLatestEdition(); - while (!stopped && (edition >= 0) && isSoneRescueMode()) { + while (!stopped && (edition >= 0) && preferences.isSoneRescueMode()) { logger.log(Level.FINE, "Downloading edition " + edition + "…"); soneDownloader.fetchSone(sone, sone.getRequestUri().setKeyType("SSK").setDocName("Sone-" + edition)); --edition; @@@ -916,7 -848,12 +926,12 @@@ * @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; } @@@ -966,6 -903,97 +981,97 @@@ } /** + * 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, preferences.getTrustComment()); + } 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, preferences.getPositiveTrust()); + } + + /** + * 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, preferences.getNegativeTrust()); + } + + /** + * 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 @@@ -973,7 -1001,7 +1079,7 @@@ */ public void updateSone(Sone sone) { if (hasSone(sone.getId())) { - boolean soneRescueMode = isLocalSone(sone) && isSoneRescueMode(); + boolean soneRescueMode = isLocalSone(sone) && preferences.isSoneRescueMode(); Sone storedSone = getSone(sone.getId()); if (!soneRescueMode && !(sone.getTime() > storedSone.getTime())) { logger.log(Level.FINE, "Downloaded Sone %s is not newer than stored Sone %s.", new Object[] { sone, storedSone }); @@@ -988,10 -1016,11 +1094,11 @@@ } } } + List storedPosts = storedSone.getPosts(); synchronized (newPosts) { for (Post post : sone.getPosts()) { - post.setSone(getSone(post.getSone().getId())); - if (!storedSone.getPosts().contains(post) && !knownPosts.contains(post.getId())) { + post.setSone(storedSone); + if (!storedPosts.contains(post) && !knownPosts.contains(post.getId())) { newPosts.add(post.getId()); coreListenerManager.fireNewPostFound(post); } @@@ -1008,10 -1037,11 +1115,11 @@@ } } } + Set storedReplies = storedSone.getReplies(); synchronized (newReplies) { for (Reply reply : sone.getReplies()) { - reply.setSone(getSone(reply.getSone().getId())); - if (!storedSone.getReplies().contains(reply) && !knownReplies.contains(reply.getId())) { + reply.setSone(storedSone); + if (!storedReplies.contains(reply) && !knownReplies.contains(reply.getId())) { newReplies.add(reply.getId()); coreListenerManager.fireNewReplyFound(reply); } @@@ -1070,8 -1100,12 +1178,12 @@@ 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) { @@@ -1080,6 -1114,23 +1192,23 @@@ } /** + * Marks the given Sone as known. If the Sone was {@link #isNewPost(String) + * new} before, a {@link CoreListener#markSoneKnown(Sone)} event is fired. + * + * @param sone + * The Sone to mark as known + */ + public void markSoneKnown(Sone sone) { + synchronized (newSones) { + if (newSones.remove(sone.getId())) { + knownSones.add(sone.getId()); + coreListenerManager.fireMarkSoneKnown(sone); + saveConfiguration(); + } + } + } + + /** * Loads and updates the given Sone from the configuration. If any error is * encountered, loading is aborted and the given Sone is not changed. * @@@ -1110,6 -1161,17 +1239,17 @@@ profile.setBirthMonth(configuration.getIntValue(sonePrefix + "/Profile/BirthMonth").getValue(null)); profile.setBirthYear(configuration.getIntValue(sonePrefix + "/Profile/BirthYear").getValue(null)); + /* load profile fields. */ + while (true) { + String fieldPrefix = sonePrefix + "/Profile/Fields/" + profile.getFields().size(); + String fieldName = configuration.getStringValue(fieldPrefix + "/Name").getValue(null); + if (fieldName == null) { + break; + } + String fieldValue = configuration.getStringValue(fieldPrefix + "/Value").getValue(""); + profile.addField(fieldName).setValue(fieldValue); + } + /* load posts. */ Set posts = new HashSet(); while (true) { @@@ -1226,8 -1288,9 +1366,9 @@@ } 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()); @@@ -1242,6 -1305,15 +1383,15 @@@ 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()) { @@@ -1289,6 -1361,8 +1439,8 @@@ 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); } } @@@ -1406,6 -1480,50 +1558,50 @@@ } /** + * Bookmarks the given post. + * + * @param post + * The post to bookmark + */ + public void bookmark(Post post) { + bookmarkPost(post.getId()); + } + + /** + * Bookmarks the post with the given ID. + * + * @param id + * The ID of the post to bookmark + */ + public void bookmarkPost(String id) { + synchronized (bookmarkedPosts) { + bookmarkedPosts.add(id); + } + } + + /** + * Removes the given post from the bookmarks. + * + * @param post + * The post to unbookmark + */ + public void unbookmark(Post post) { + unbookmarkPost(post.getId()); + } + + /** + * Removes the post with the given ID from the bookmarks. + * + * @param id + * The ID of the post to unbookmark + */ + public void unbookmarkPost(String id) { + synchronized (bookmarkedPosts) { + bookmarkedPosts.remove(id); + } + } + + /** * Creates a new reply. * * @param sone @@@ -1487,40 -1605,6 +1683,40 @@@ } /** + * Creates a new top-level album for the given Sone. + * + * @param sone + * The Sone to create the album for + * @return The new album + */ + public Album createAlbum(Sone sone) { + return createAlbum(sone, null); + } + + /** + * Creates a new album for the given Sone. + * + * @param sone + * The Sone to create the album for + * @param parent + * The parent of the album (may be {@code null} to create a + * top-level album) + * @return The new album + */ + public Album createAlbum(Sone sone, Album parent) { + Album album = new Album(); + synchronized (albums) { + albums.put(album.getId(), album); + } + album.setSone(sone); + if (parent != null) { + parent.addAlbum(album); + } + sone.addAlbum(album); + return album; + } + + /** * Starts the core. */ public void start() { @@@ -1561,6 -1645,9 +1757,9 @@@ 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()); @@@ -1592,6 -1679,15 +1791,15 @@@ configuration.getStringValue("KnownReplies/" + replyCounter + "/ID").setValue(null); } + /* save bookmarked posts. */ + int bookmarkedPostCounter = 0; + synchronized (bookmarkedPosts) { + for (String bookmarkedPostId : bookmarkedPosts) { + configuration.getStringValue("Bookmarks/Post/" + bookmarkedPostCounter++ + "/ID").setValue(bookmarkedPostId); + } + } + configuration.getStringValue("Bookmarks/Post/" + bookmarkedPostCounter++ + "/ID").setValue(null); + /* now save it. */ configuration.save(); @@@ -1622,6 -1718,9 +1830,9 @@@ } })); + options.addIntegerOption("PositiveTrust", new DefaultOption(75)); + options.addIntegerOption("NegativeTrust", new DefaultOption(-100)); + options.addStringOption("TrustComment", new DefaultOption("Set from Sone Web Interface")); options.addBooleanOption("SoneRescueMode", new DefaultOption(false)); options.addBooleanOption("ClearOnNextRestart", new DefaultOption(false)); options.addBooleanOption("ReallyClearOnNextRestart", new DefaultOption(false)); @@@ -1638,6 -1737,9 +1849,9 @@@ } 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. */ @@@ -1676,6 -1778,18 +1890,18 @@@ } } + /* load bookmarked posts. */ + int bookmarkedPostCounter = 0; + while (true) { + String bookmarkedPostId = configuration.getStringValue("Bookmarks/Post/" + bookmarkedPostCounter++ + "/ID").getValue(null); + if (bookmarkedPostId == null) { + break; + } + synchronized (bookmarkedPosts) { + bookmarkedPosts.add(bookmarkedPostId); + } + } + } /** @@@ -1706,6 -1820,7 +1932,7 @@@ public void ownIdentityAdded(OwnIdentity ownIdentity) { logger.log(Level.FINEST, "Adding OwnIdentity: " + ownIdentity); if (ownIdentity.hasContext("Sone")) { + trustedIdentities.put(ownIdentity, Collections.synchronizedSet(new HashSet())); addLocalSone(ownIdentity); } } @@@ -1716,14 -1831,16 +1943,16 @@@ @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); } @@@ -1731,13 -1848,15 +1960,15 @@@ * {@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.addSone(sone); soneDownloader.fetchSone(sone); } }).start(); @@@ -1747,8 -1866,8 +1978,8 @@@ * {@inheritDoc} */ @Override - public void identityRemoved(Identity identity) { - /* TODO */ + public void identityRemoved(OwnIdentity ownIdentity, Identity identity) { + trustedIdentities.get(ownIdentity).remove(identity); } // @@@ -1759,8 -1878,195 +1990,195 @@@ * {@inheritDoc} */ @Override - public void updateFound(Version version, long releaseTime) { - coreListenerManager.fireUpdateFound(version, releaseTime); + public void updateFound(Version version, long releaseTime, long latestEdition) { + coreListenerManager.fireUpdateFound(version, releaseTime, latestEdition); + } + + /** + * Convenience interface for external classes that want to access the core’s + * configuration. + * + * @author David ‘Bombe’ Roden + */ + public static class Preferences { + + /** The wrapped options. */ + private final Options options; + + /** + * Creates a new preferences object wrapped around the given options. + * + * @param options + * The options to wrap + */ + public Preferences(Options options) { + this.options = options; + } + + /** + * Returns the insertion delay. + * + * @return The insertion delay + */ + public int getInsertionDelay() { + return options.getIntegerOption("InsertionDelay").get(); + } + + /** + * Sets the insertion delay + * + * @param insertionDelay + * The new insertion delay, or {@code null} to restore it to + * the default value + * @return This preferences + */ + public Preferences setInsertionDelay(Integer insertionDelay) { + options.getIntegerOption("InsertionDelay").set(insertionDelay); + return this; + } + + /** + * Returns the positive trust. + * + * @return The positive trust + */ + public int getPositiveTrust() { + return options.getIntegerOption("PositiveTrust").get(); + } + + /** + * Sets the positive trust. + * + * @param positiveTrust + * The new positive trust, or {@code null} to restore it to + * the default vlaue + * @return This preferences + */ + public Preferences setPositiveTrust(Integer positiveTrust) { + options.getIntegerOption("PositiveTrust").set(positiveTrust); + return this; + } + + /** + * Returns the negative trust. + * + * @return The negative trust + */ + public int getNegativeTrust() { + return options.getIntegerOption("NegativeTrust").get(); + } + + /** + * Sets the negative trust. + * + * @param negativeTrust + * The negative trust, or {@code null} to restore it to the + * default value + * @return The preferences + */ + public Preferences setNegativeTrust(Integer negativeTrust) { + options.getIntegerOption("NegativeTrust").set(negativeTrust); + return this; + } + + /** + * Returns the trust comment. This is the comment that is set in the web + * of trust when a trust value is assigned to an identity. + * + * @return The trust comment + */ + public String getTrustComment() { + return options.getStringOption("TrustComment").get(); + } + + /** + * Sets the trust comment. + * + * @param trustComment + * The trust comment, or {@code null} to restore it to the + * default value + * @return This preferences + */ + public Preferences setTrustComment(String trustComment) { + options.getStringOption("TrustComment").set(trustComment); + return this; + } + + /** + * Returns whether the rescue mode is active. + * + * @return {@code true} if the rescue mode is active, {@code false} + * otherwise + */ + public boolean isSoneRescueMode() { + return options.getBooleanOption("SoneRescueMode").get(); + } + + /** + * Sets whether the rescue mode is active. + * + * @param soneRescueMode + * {@code true} if the rescue mode is active, {@code false} + * otherwise + * @return This preferences + */ + public Preferences setSoneRescueMode(Boolean soneRescueMode) { + options.getBooleanOption("SoneRescueMode").set(soneRescueMode); + return this; + } + + /** + * Returns whether Sone should clear its settings on the next restart. + * In order to be effective, {@link #isReallyClearOnNextRestart()} needs + * to return {@code true} as well! + * + * @return {@code true} if Sone should clear its settings on the next + * restart, {@code false} otherwise + */ + public boolean isClearOnNextRestart() { + return options.getBooleanOption("ClearOnNextRestart").get(); + } + + /** + * Sets whether Sone will clear its settings on the next restart. + * + * @param clearOnNextRestart + * {@code true} if Sone should clear its settings on the next + * restart, {@code false} otherwise + * @return This preferences + */ + public Preferences setClearOnNextRestart(Boolean clearOnNextRestart) { + options.getBooleanOption("ClearOnNextRestart").set(clearOnNextRestart); + return this; + } + + /** + * Returns whether Sone should really clear its settings on next + * restart. This is a confirmation option that needs to be set in + * addition to {@link #isClearOnNextRestart()} in order to clear Sone’s + * settings on the next restart. + * + * @return {@code true} if Sone should really clear its settings on the + * next restart, {@code false} otherwise + */ + public boolean isReallyClearOnNextRestart() { + return options.getBooleanOption("ReallyClearOnNextRestart").get(); + } + + /** + * Sets whether Sone should really clear its settings on the next + * restart. + * + * @param reallyClearOnNextRestart + * {@code true} if Sone should really clear its settings on + * the next restart, {@code false} otherwise + * @return This preferences + */ + public Preferences setReallyClearOnNextRestart(Boolean reallyClearOnNextRestart) { + options.getBooleanOption("ReallyClearOnNextRestart").set(reallyClearOnNextRestart); + return this; + } + } } diff --combined src/main/java/net/pterodactylus/sone/data/Sone.java index 91b1c10,cc54697..23bbb3e --- a/src/main/java/net/pterodactylus/sone/data/Sone.java +++ b/src/main/java/net/pterodactylus/sone/data/Sone.java @@@ -30,7 -30,6 +30,7 @@@ import java.util.logging.Logger import net.pterodactylus.sone.freenet.wot.Identity; import net.pterodactylus.sone.template.SoneAccessor; import net.pterodactylus.util.logging.Logging; +import net.pterodactylus.util.validation.Validation; import freenet.keys.FreenetURI; /** @@@ -41,7 -40,7 +41,7 @@@ * * @author David ‘Bombe’ Roden */ - public class Sone implements Fingerprintable { + public class Sone implements Fingerprintable, Comparable { /** comparator that sorts Sones by their nice name. */ public static final Comparator NICE_NAME_COMPARATOR = new Comparator() { @@@ -100,9 -99,6 +100,9 @@@ /** The IDs of all liked replies. */ private final Set likedReplyIds = Collections.synchronizedSet(new HashSet()); + /** The albums of this Sone. */ + private final List albums = Collections.synchronizedList(new ArrayList()); + /** * Creates a new Sone. * @@@ -180,7 -176,7 +180,7 @@@ */ public Sone setRequestUri(FreenetURI requestUri) { if (this.requestUri == null) { - this.requestUri = requestUri.setDocName("Sone").setMetaString(new String[0]); + this.requestUri = requestUri.setKeyType("USK").setDocName("Sone").setMetaString(new String[0]); return this; } if (!this.requestUri.equalsKeypair(requestUri)) { @@@ -208,7 -204,7 +208,7 @@@ */ public Sone setInsertUri(FreenetURI insertUri) { if (this.insertUri == null) { - this.insertUri = insertUri.setDocName("Sone").setMetaString(new String[0]); + this.insertUri = insertUri.setKeyType("USK").setDocName("Sone").setMetaString(new String[0]); return this; } if (!this.insertUri.equalsKeypair(insertUri)) { @@@ -584,37 -580,6 +584,37 @@@ return this; } + /** + * Returns the albums of this Sone. + * + * @return The albums of this Sone + */ + public List getAlbums() { + return Collections.unmodifiableList(albums); + } + + /** + * Adds an album to this Sone. + * + * @param album + * The album to add + */ + public synchronized void addAlbum(Album album) { + Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).check(); + albums.add(album); + } + + /** + * Removes an album from this Sone. + * + * @param album + * The album to remove + */ + public synchronized void removeAlbum(Album album) { + Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).check(); + albums.remove(album); + } + // // FINGERPRINTABLE METHODS // @@@ -625,26 -590,7 +625,7 @@@ @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()) { @@@ -676,16 -622,22 +657,28 @@@ } fingerprint.append(')'); + fingerprint.append("Albums("); + for (Album album : albums) { + fingerprint.append(album.getFingerprint()); + } + fingerprint.append(')'); + return fingerprint.toString(); } // + // INTERFACE Comparable + // + + /** + * {@inheritDoc} + */ + @Override + public int compareTo(Sone sone) { + return NICE_NAME_COMPARATOR.compare(this, sone); + } + + // // OBJECT METHODS // diff --combined src/main/java/net/pterodactylus/sone/template/AlbumAccessor.java index e2c6f16,0000000..5042995 mode 100644,000000..100644 --- a/src/main/java/net/pterodactylus/sone/template/AlbumAccessor.java +++ b/src/main/java/net/pterodactylus/sone/template/AlbumAccessor.java @@@ -1,78 -1,0 +1,78 @@@ +/* + * Sone - AlbumAccessor.java - Copyright © 2011 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.template; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import net.pterodactylus.sone.data.Album; +import net.pterodactylus.util.template.Accessor; - import net.pterodactylus.util.template.DataProvider; +import net.pterodactylus.util.template.ReflectionAccessor; ++import net.pterodactylus.util.template.TemplateContext; + +/** + * {@link Accessor} implementation for {@link Album}s. A property named + * “backlinks” is added, it returns links to all parents and the owner Sone of + * an album. + * + * @author David ‘Bombe’ Roden + */ +public class AlbumAccessor extends ReflectionAccessor { + + /** + * {@inheritDoc} + */ + @Override - public Object get(DataProvider dataProvider, Object object, String member) { ++ public Object get(TemplateContext templateContext, Object object, String member) { + Album album = (Album) object; + if ("backlinks".equals(member)) { + List> backlinks = new ArrayList>(); + Album currentAlbum = album; + while (currentAlbum != null) { + backlinks.add(0, createLink("imageBrowser.html?album=" + album.getId(), album.getName())); + currentAlbum = currentAlbum.getParent(); + } + backlinks.add(0, createLink("viewSone.html?sone=" + album.getSone().getId(), SoneAccessor.getNiceName(album.getSone()))); + return backlinks; + } - return super.get(dataProvider, object, member); ++ return super.get(templateContext, object, member); + } + + // + // PRIVATE METHODS + // + + /** + * Creates a map containing mappings for “target” and “link.” + * + * @param target + * The target to link to + * @param name + * The name of the link + * @return The created map containing the mappings + */ + private Map createLink(String target, String name) { + Map link = new HashMap(); + link.put("target", target); + link.put("name", name); + return link; + } + +} diff --combined src/main/java/net/pterodactylus/sone/web/CreateAlbumPage.java index 7e1062d,0000000..7b51fb5 mode 100644,000000..100644 --- a/src/main/java/net/pterodactylus/sone/web/CreateAlbumPage.java +++ b/src/main/java/net/pterodactylus/sone/web/CreateAlbumPage.java @@@ -1,70 -1,0 +1,70 @@@ +/* + * Sone - CreateAlbumPage.java - Copyright © 2011 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web; + +import net.pterodactylus.sone.data.Album; +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; ++import net.pterodactylus.util.template.TemplateContext; + +/** + * Page that lets the user create a new album. + * + * @author David ‘Bombe’ Roden + */ +public class CreateAlbumPage extends SoneTemplatePage { + + /** + * Creates a new “create album” page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public CreateAlbumPage(Template template, WebInterface webInterface) { + super("createAlbum.html", template, "Page.CreateAlbum.Title", webInterface, true); + } + + // + // SONETEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override - protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException { - super.processTemplate(request, dataProvider); ++ protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException { ++ super.processTemplate(request, templateContext); + if (request.getMethod() == Method.POST) { + String name = request.getHttpRequest().getPartAsStringFailsafe("name", 64).trim(); + if (name.length() == 0) { - dataProvider.set("nameMissing", true); ++ templateContext.set("nameMissing", true); + return; + } + Sone currentSone = getCurrentSone(request.getToadletContext()); + String parentId = request.getHttpRequest().getPartAsStringFailsafe("parent", 36); + Album parent = webInterface.getCore().getAlbum(parentId, false); + Album album = webInterface.getCore().createAlbum(currentSone, parent); + album.setName(name); + throw new RedirectException("imageBrowser.html?album=" + album.getId()); + } + } + +} diff --combined src/main/java/net/pterodactylus/sone/web/ImageBrowserPage.java index c204128,0000000..6da7cda mode 100644,000000..100644 --- a/src/main/java/net/pterodactylus/sone/web/ImageBrowserPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ImageBrowserPage.java @@@ -1,68 -1,0 +1,68 @@@ +/* + * Sone - ImageBrowserPage.java - Copyright © 2011 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web; + +import net.pterodactylus.sone.data.Album; +import net.pterodactylus.sone.data.Image; - import net.pterodactylus.util.template.DataProvider; +import net.pterodactylus.util.template.Template; ++import net.pterodactylus.util.template.TemplateContext; + +/** + * The image browser page is the entry page for the image management. + * + * @author David ‘Bombe’ Roden + */ +public class ImageBrowserPage extends SoneTemplatePage { + + /** + * Creates a new image browser page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public ImageBrowserPage(Template template, WebInterface webInterface) { + super("imageBrowser.html", template, "Page.ImageBrowser.Title", webInterface, true); + } + + // + // SONETEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override - protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException { - super.processTemplate(request, dataProvider); ++ protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException { ++ super.processTemplate(request, templateContext); + String albumId = request.getHttpRequest().getParam("album", null); + if (albumId != null) { + Album album = webInterface.getCore().getAlbum(albumId, false); - dataProvider.set("albumRequested", true); - dataProvider.set("album", album); ++ templateContext.set("albumRequested", true); ++ templateContext.set("album", album); + return; + } + String imageId = request.getHttpRequest().getParam("image", null); + if (imageId != null) { + Image image = webInterface.getCore().getImage(imageId, false); - dataProvider.set("imageRequested", true); - dataProvider.set("image", image); ++ templateContext.set("imageRequested", true); ++ templateContext.set("image", image); + } + } +} diff --combined src/main/java/net/pterodactylus/sone/web/WebInterface.java index 66ba789,89cec92..e3a95d4 --- a/src/main/java/net/pterodactylus/sone/web/WebInterface.java +++ b/src/main/java/net/pterodactylus/sone/web/WebInterface.java @@@ -36,30 -36,37 +36,39 @@@ import java.util.logging.Logger import net.pterodactylus.sone.core.Core; import net.pterodactylus.sone.core.CoreListener; +import net.pterodactylus.sone.data.Album; import net.pterodactylus.sone.data.Post; import net.pterodactylus.sone.data.Reply; import net.pterodactylus.sone.data.Sone; import net.pterodactylus.sone.freenet.L10nFilter; import net.pterodactylus.sone.freenet.wot.Identity; + import net.pterodactylus.sone.freenet.wot.Trust; import net.pterodactylus.sone.main.SonePlugin; import net.pterodactylus.sone.notify.ListNotification; +import net.pterodactylus.sone.template.AlbumAccessor; 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.ParserFilter; 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.template.UnknownDateFilter; + import net.pterodactylus.sone.web.ajax.BookmarkAjaxPage; 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; @@@ -68,27 -75,42 +77,42 @@@ import net.pterodactylus.sone.web.ajax. import net.pterodactylus.sone.web.ajax.GetTranslationPage; import net.pterodactylus.sone.web.ajax.LikeAjaxPage; import net.pterodactylus.sone.web.ajax.LockSoneAjaxPage; - import net.pterodactylus.sone.web.ajax.MarkPostAsKnownPage; - import net.pterodactylus.sone.web.ajax.MarkReplyAsKnownPage; + import net.pterodactylus.sone.web.ajax.MarkAsKnownAjaxPage; + import net.pterodactylus.sone.web.ajax.MoveProfileFieldAjaxPage; + import net.pterodactylus.sone.web.ajax.TrustAjaxPage; + import net.pterodactylus.sone.web.ajax.UnbookmarkAjaxPage; 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; + import net.pterodactylus.util.cache.Cache; + 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.logging.Logging; import net.pterodactylus.util.notify.Notification; import net.pterodactylus.util.notify.NotificationManager; import net.pterodactylus.util.notify.TemplateNotification; + import net.pterodactylus.util.template.CollectionSortFilter; import net.pterodactylus.util.template.DateFilter; - import net.pterodactylus.util.template.DefaultTemplateFactory; + import net.pterodactylus.util.template.FormatFilter; + import net.pterodactylus.util.template.HtmlFilter; import net.pterodactylus.util.template.MatchFilter; import net.pterodactylus.util.template.PaginationPlugin; + import net.pterodactylus.util.template.Provider; import net.pterodactylus.util.template.ReflectionAccessor; + import net.pterodactylus.util.template.ReplaceFilter; + import net.pterodactylus.util.template.StoreFilter; import net.pterodactylus.util.template.Template; + import net.pterodactylus.util.template.TemplateContext; + import net.pterodactylus.util.template.TemplateContextFactory; import net.pterodactylus.util.template.TemplateException; - import net.pterodactylus.util.template.TemplateFactory; - import net.pterodactylus.util.template.TemplateProvider; + import net.pterodactylus.util.template.TemplateParser; import net.pterodactylus.util.template.XmlFilter; import net.pterodactylus.util.thread.Ticker; import net.pterodactylus.util.version.Version; @@@ -121,8 -143,8 +145,8 @@@ public class WebInterface implements Co /** The form password. */ private final String formPassword; - /** The template factory. */ - private DefaultTemplateFactory templateFactory; + /** The template context factory. */ + private final TemplateContextFactory templateContextFactory; /** The “new Sone” notification. */ private final ListNotification newSoneNotification; @@@ -154,51 -176,61 +178,62 @@@ * @param sonePlugin * The Sone plugin */ + @SuppressWarnings("synthetic-access") public WebInterface(SonePlugin sonePlugin) { this.sonePlugin = sonePlugin; formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword(); - templateFactory = new DefaultTemplateFactory(); - templateFactory.addAccessor(Object.class, new ReflectionAccessor()); - templateFactory.addAccessor(Collection.class, new CollectionAccessor()); - templateFactory.addAccessor(Sone.class, new SoneAccessor(getCore())); - templateFactory.addAccessor(Post.class, new PostAccessor(getCore(), templateFactory)); - templateFactory.addAccessor(Reply.class, new ReplyAccessor(getCore(), templateFactory)); - templateFactory.addAccessor(Album.class, new AlbumAccessor()); - templateFactory.addAccessor(Identity.class, new IdentityAccessor(getCore())); - templateFactory.addAccessor(NotificationManager.class, new NotificationManagerAccessor()); - templateFactory.addFilter("date", new DateFilter()); - templateFactory.addFilter("l10n", new L10nFilter(getL10n())); - templateFactory.addFilter("substring", new SubstringFilter()); - templateFactory.addFilter("xml", new XmlFilter()); - templateFactory.addFilter("change", new RequestChangeFilter()); - templateFactory.addFilter("match", new MatchFilter()); - templateFactory.addFilter("css", new CssClassNameFilter()); - templateFactory.addPlugin("getpage", new GetPagePlugin()); - templateFactory.addPlugin("paginate", new PaginationPlugin()); - templateFactory.setTemplateProvider(new ClassPathTemplateProvider(templateFactory)); - templateFactory.addTemplateObject("formPassword", formPassword); + templateContextFactory = new TemplateContextFactory(); + templateContextFactory.addAccessor(Object.class, new ReflectionAccessor()); + templateContextFactory.addAccessor(Collection.class, new CollectionAccessor()); + templateContextFactory.addAccessor(Sone.class, new SoneAccessor(getCore())); + templateContextFactory.addAccessor(Post.class, new PostAccessor(getCore())); + templateContextFactory.addAccessor(Reply.class, new ReplyAccessor(getCore())); ++ templateContextFactory.addAccessor(Album.class, new AlbumAccessor()); + templateContextFactory.addAccessor(Identity.class, new IdentityAccessor(getCore())); + templateContextFactory.addAccessor(NotificationManager.class, new NotificationManagerAccessor()); + templateContextFactory.addAccessor(Trust.class, new TrustAccessor()); + templateContextFactory.addFilter("date", new DateFilter()); + templateContextFactory.addFilter("html", new HtmlFilter()); + templateContextFactory.addFilter("replace", new ReplaceFilter()); + templateContextFactory.addFilter("store", new StoreFilter()); + templateContextFactory.addFilter("l10n", new L10nFilter(getL10n())); + templateContextFactory.addFilter("substring", new SubstringFilter()); + templateContextFactory.addFilter("xml", new XmlFilter()); + templateContextFactory.addFilter("change", new RequestChangeFilter()); + templateContextFactory.addFilter("match", new MatchFilter()); + templateContextFactory.addFilter("css", new CssClassNameFilter()); + templateContextFactory.addFilter("js", new JavascriptFilter()); + templateContextFactory.addFilter("parse", new ParserFilter(getCore(), templateContextFactory)); + templateContextFactory.addFilter("unknown", new UnknownDateFilter(getL10n(), "View.Sone.Text.UnknownDate")); + templateContextFactory.addFilter("format", new FormatFilter()); + templateContextFactory.addFilter("sort", new CollectionSortFilter()); + templateContextFactory.addPlugin("getpage", new GetPagePlugin()); + templateContextFactory.addPlugin("paginate", new PaginationPlugin()); + templateContextFactory.addProvider(Provider.TEMPLATE_CONTEXT_PROVIDER); + templateContextFactory.addProvider(new ClassPathTemplateProvider()); + templateContextFactory.addTemplateObject("formPassword", formPassword); /* create notifications. */ - Template newSoneNotificationTemplate = templateFactory.createTemplate(createReader("/templates/notify/newSoneNotification.html")); + Template newSoneNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/newSoneNotification.html")); newSoneNotification = new ListNotification("new-sone-notification", "sones", newSoneNotificationTemplate); - Template newPostNotificationTemplate = templateFactory.createTemplate(createReader("/templates/notify/newPostNotification.html")); + Template newPostNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/newPostNotification.html")); newPostNotification = new ListNotification("new-post-notification", "posts", newPostNotificationTemplate); - Template newReplyNotificationTemplate = templateFactory.createTemplate(createReader("/templates/notify/newReplyNotification.html")); + Template newReplyNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/newReplyNotification.html")); newReplyNotification = new ListNotification("new-replies-notification", "replies", newReplyNotificationTemplate); - Template rescuingSonesTemplate = templateFactory.createTemplate(createReader("/templates/notify/rescuingSonesNotification.html")); + Template rescuingSonesTemplate = TemplateParser.parse(createReader("/templates/notify/rescuingSonesNotification.html")); rescuingSonesNotification = new ListNotification("sones-being-rescued-notification", "sones", rescuingSonesTemplate); - Template sonesRescuedTemplate = templateFactory.createTemplate(createReader("/templates/notify/sonesRescuedNotification.html")); + Template sonesRescuedTemplate = TemplateParser.parse(createReader("/templates/notify/sonesRescuedNotification.html")); sonesRescuedNotification = new ListNotification("sones-rescued-notification", "sones", sonesRescuedTemplate); - Template lockedSonesTemplate = templateFactory.createTemplate(createReader("/templates/notify/lockedSonesNotification.html")); + Template lockedSonesTemplate = TemplateParser.parse(createReader("/templates/notify/lockedSonesNotification.html")); lockedSonesNotification = new ListNotification("sones-locked-notification", "sones", lockedSonesTemplate); - Template newVersionTemplate = templateFactory.createTemplate(createReader("/templates/notify/newVersionNotification.html")); + Template newVersionTemplate = TemplateParser.parse(createReader("/templates/notify/newVersionNotification.html")); newVersionNotification = new TemplateNotification("new-version-notification", newVersionTemplate); } @@@ -216,6 -248,15 +251,15 @@@ } /** + * Returns the template context factory of the web interface. + * + * @return The template context factory + */ + public TemplateContextFactory getTemplateContextFactory() { + return templateContextFactory; + } + + /** * Returns the current session, creating a new session if there is no * current session. * @@@ -272,6 -313,10 +316,10 @@@ * currently logged in */ public Sone getCurrentSone(ToadletContext toadletContext, boolean create) { + Set localSones = getCore().getLocalSones(); + if (localSones.size() == 1) { + return localSones.iterator().next(); + } return getCurrentSone(getCurrentSession(toadletContext, create)); } @@@ -377,7 -422,7 +425,7 @@@ */ public void setFirstStart(boolean firstStart) { if (firstStart) { - Template firstStartNotificationTemplate = templateFactory.createTemplate(createReader("/templates/notify/firstStartNotification.html")); + Template firstStartNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/firstStartNotification.html")); Notification firstStartNotification = new TemplateNotification("first-start-notification", firstStartNotificationTemplate); notificationManager.addNotification(firstStartNotification); } @@@ -392,7 -437,7 +440,7 @@@ */ public void setNewConfig(boolean newConfig) { if (newConfig && !hasFirstStartNotification()) { - Template configNotReadNotificationTemplate = templateFactory.createTemplate(createReader("/templates/notify/configNotReadNotification.html")); + Template configNotReadNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/configNotReadNotification.html")); Notification configNotReadNotification = new TemplateNotification("config-not-read-notification", configNotReadNotificationTemplate); notificationManager.addNotification(configNotReadNotification); } @@@ -423,7 -468,7 +471,7 @@@ registerToadlets(); /* notification templates. */ - Template startupNotificationTemplate = templateFactory.createTemplate(createReader("/templates/notify/startupNotification.html")); + Template startupNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/startupNotification.html")); final TemplateNotification startupNotification = new TemplateNotification("startup-notification", startupNotificationTemplate); notificationManager.addNotification(startupNotification); @@@ -436,7 -481,7 +484,7 @@@ } }, "Sone Startup Notification Remover"); - Template wotMissingNotificationTemplate = templateFactory.createTemplate(createReader("/templates/notify/wotMissingNotification.html")); + Template wotMissingNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/wotMissingNotification.html")); final TemplateNotification wotMissingNotification = new TemplateNotification("wot-missing-notification", wotMissingNotificationTemplate); Ticker.getInstance().registerEvent(System.currentTimeMillis() + (15 * 1000), new Runnable() { @@@ -470,32 -515,36 +518,38 @@@ * 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 createSoneTemplate = templateFactory.createTemplate(createReader("/templates/createSone.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 viewSoneTemplate = templateFactory.createTemplate(createReader("/templates/viewSone.html")); - Template viewPostTemplate = templateFactory.createTemplate(createReader("/templates/viewPost.html")); - Template deletePostTemplate = templateFactory.createTemplate(createReader("/templates/deletePost.html")); - Template deleteReplyTemplate = templateFactory.createTemplate(createReader("/templates/deleteReply.html")); - Template deleteSoneTemplate = templateFactory.createTemplate(createReader("/templates/deleteSone.html")); - Template imageBrowserTemplate = templateFactory.createTemplate(createReader("/templates/imageBrowser.html")); - Template createAlbumTemplate = templateFactory.createTemplate(createReader("/templates/createAlbum.html")); - Template noPermissionTemplate = templateFactory.createTemplate(createReader("/templates/noPermission.html")); - Template optionsTemplate = templateFactory.createTemplate(createReader("/templates/options.html")); - Template aboutTemplate = templateFactory.createTemplate(createReader("/templates/about.html")); - Template postTemplate = templateFactory.createTemplate(createReader("/templates/include/viewPost.html")); - Template replyTemplate = templateFactory.createTemplate(createReader("/templates/include/viewReply.html")); + Template emptyTemplate = TemplateParser.parse(new StringReader("")); + Template loginTemplate = TemplateParser.parse(createReader("/templates/login.html")); + Template indexTemplate = TemplateParser.parse(createReader("/templates/index.html")); + Template knownSonesTemplate = TemplateParser.parse(createReader("/templates/knownSones.html")); + Template createSoneTemplate = TemplateParser.parse(createReader("/templates/createSone.html")); + Template createPostTemplate = TemplateParser.parse(createReader("/templates/createPost.html")); + Template createReplyTemplate = TemplateParser.parse(createReader("/templates/createReply.html")); + Template bookmarksTemplate = TemplateParser.parse(createReader("/templates/bookmarks.html")); + Template editProfileTemplate = TemplateParser.parse(createReader("/templates/editProfile.html")); + Template editProfileFieldTemplate = TemplateParser.parse(createReader("/templates/editProfileField.html")); + Template deleteProfileFieldTemplate = TemplateParser.parse(createReader("/templates/deleteProfileField.html")); + Template viewSoneTemplate = TemplateParser.parse(createReader("/templates/viewSone.html")); + Template viewPostTemplate = TemplateParser.parse(createReader("/templates/viewPost.html")); + Template deletePostTemplate = TemplateParser.parse(createReader("/templates/deletePost.html")); + Template deleteReplyTemplate = TemplateParser.parse(createReader("/templates/deleteReply.html")); + Template deleteSoneTemplate = TemplateParser.parse(createReader("/templates/deleteSone.html")); ++ Template imageBrowserTemplate = TemplateParser.parse(createReader("/templates/imageBrowser.html")); ++ Template createAlbumTemplate = TemplateParser.parse(createReader("/templates/createAlbum.html")); + Template noPermissionTemplate = TemplateParser.parse(createReader("/templates/noPermission.html")); + Template optionsTemplate = TemplateParser.parse(createReader("/templates/options.html")); + Template aboutTemplate = TemplateParser.parse(createReader("/templates/about.html")); + Template invalidTemplate = TemplateParser.parse(createReader("/templates/invalid.html")); + Template postTemplate = TemplateParser.parse(createReader("/templates/include/viewPost.html")); + Template replyTemplate = TemplateParser.parse(createReader("/templates/include/viewReply.html")); PageToadletFactory pageToadletFactory = new PageToadletFactory(sonePlugin.pluginRespirator().getHLSimpleClient(), "/Sone/"); pageToadlets.add(pageToadletFactory.createPageToadlet(new IndexPage(indexTemplate, this), "Index")); 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))); @@@ -508,8 -557,13 +562,15 @@@ 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 ImageBrowserPage(imageBrowserTemplate, this), "ImageBrowser")); + pageToadlets.add(pageToadletFactory.createPageToadlet(new CreateAlbumPage(createAlbumTemplate, 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 MarkAsKnownPage(emptyTemplate, this))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new BookmarkPage(emptyTemplate, this))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new UnbookmarkPage(emptyTemplate, this))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new BookmarksPage(bookmarksTemplate, this), "Bookmarks")); pageToadlets.add(pageToadletFactory.createPageToadlet(new DeleteSonePage(deleteSoneTemplate, this), "DeleteSone")); pageToadlets.add(pageToadletFactory.createPageToadlet(new LoginPage(loginTemplate, this), "Login")); pageToadlets.add(pageToadletFactory.createPageToadlet(new LogoutPage(emptyTemplate, this), "Logout")); @@@ -517,6 -571,7 +578,7 @@@ 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(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"))); @@@ -527,17 -582,24 +589,24 @@@ pageToadlets.add(pageToadletFactory.createPageToadlet(new CreateReplyAjaxPage(this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new GetReplyAjaxPage(this, replyTemplate))); pageToadlets.add(pageToadletFactory.createPageToadlet(new GetPostAjaxPage(this, postTemplate))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new MarkPostAsKnownPage(this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new MarkReplyAsKnownPage(this))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new MarkAsKnownAjaxPage(this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new DeletePostAjaxPage(this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new DeleteReplyAjaxPage(this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new LockSoneAjaxPage(this))); 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 BookmarkAjaxPage(this))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new UnbookmarkAjaxPage(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); @@@ -574,6 -636,7 +643,7 @@@ try { return new InputStreamReader(getClass().getResourceAsStream(resourceName), "UTF-8"); } catch (UnsupportedEncodingException uee1) { + System.out.println(" fail."); return null; } } @@@ -712,9 -775,10 +782,10 @@@ * {@inheritDoc} */ @Override - public void updateFound(Version version, long releaseTime) { - newVersionNotification.set("version", version); - newVersionNotification.set("releaseTime", releaseTime); + public void updateFound(Version version, long releaseTime, long latestEdition) { + newVersionNotification.getTemplateContext().set("latestVersion", version); + newVersionNotification.getTemplateContext().set("latestEdition", latestEdition); + newVersionNotification.getTemplateContext().set("releaseTime", releaseTime); notificationManager.addNotification(newVersionNotification); } @@@ -725,36 -789,53 +796,53 @@@ * * @author David ‘Bombe’ Roden */ - private class ClassPathTemplateProvider implements TemplateProvider { + private class ClassPathTemplateProvider implements Provider { - /** The template factory. */ - @SuppressWarnings("hiding") - private final TemplateFactory templateFactory; + /** Cache for templates. */ + private final Cache templateCache = new MemoryCache(new ValueRetriever() { + + @Override + @SuppressWarnings("synthetic-access") + public CacheItem