X-Git-Url: https://git.pterodactylus.net/?p=Sone.git;a=blobdiff_plain;f=src%2Fmain%2Fjava%2Fnet%2Fpterodactylus%2Fsone%2Fcore%2FCore.java;h=ea36aad76bf88c22ff6376495271d050c5133f61;hp=0685c9ac5d84ac8b93ea96473dd7a18b2fe665a4;hb=bb2c8cb2d47a77bcef2d299ad4bf8e16f22a6198;hpb=b72db16cd16e8bfbaadc44604bdff3f49a1aff51 diff --git a/src/main/java/net/pterodactylus/sone/core/Core.java b/src/main/java/net/pterodactylus/sone/core/Core.java index 0685c9a..ea36aad 100644 --- a/src/main/java/net/pterodactylus/sone/core/Core.java +++ b/src/main/java/net/pterodactylus/sone/core/Core.java @@ -40,6 +40,8 @@ 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.fcp.FcpInterface; +import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired; import net.pterodactylus.sone.freenet.wot.Identity; import net.pterodactylus.sone.freenet.wot.IdentityListener; import net.pterodactylus.sone.freenet.wot.IdentityManager; @@ -51,6 +53,10 @@ 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.thread.Ticker; +import net.pterodactylus.util.validation.EqualityValidator; +import net.pterodactylus.util.validation.IntegerRangeValidator; +import net.pterodactylus.util.validation.OrValidator; import net.pterodactylus.util.validation.Validation; import net.pterodactylus.util.version.Version; import freenet.keys.FreenetURI; @@ -60,7 +66,7 @@ import freenet.keys.FreenetURI; * * @author David ‘Bombe’ Roden */ -public class Core implements IdentityListener, UpdateListener { +public class Core implements IdentityListener, UpdateListener, SoneProvider, PostProvider { /** * Enumeration for the possible states of a {@link Sone}. @@ -115,7 +121,11 @@ public class Core implements IdentityListener, UpdateListener { /** The update checker. */ private final UpdateChecker updateChecker; + /** The FCP interface. */ + private volatile FcpInterface fcpInterface; + /** Whether the core has been stopped. */ + @SuppressWarnings("unused") private volatile boolean stopped; /** The Sones’ statuses. */ @@ -130,6 +140,10 @@ public class Core implements IdentityListener, UpdateListener { /* synchronize access on this on localSones. */ private final Map soneInserters = new HashMap(); + /** Sone rescuers. */ + /* synchronize access on this on localSones. */ + private final Map soneRescuers = new HashMap(); + /** All local Sones. */ /* synchronize access on this on itself. */ private Map localSones = new HashMap(); @@ -171,6 +185,9 @@ public class Core implements IdentityListener, UpdateListener { /** Trusted identities, sorted by own identities. */ private Map> trustedIdentities = Collections.synchronizedMap(new HashMap>()); + /** Ticker for threads that mark own elements as known. */ + private Ticker localElementTicker = new Ticker(); + /** * Creates a new core. * @@ -257,6 +274,16 @@ public class Core implements IdentityListener, UpdateListener { } /** + * Sets the FCP interface to use. + * + * @param fcpInterface + * The FCP interface to use + */ + public void setFcpInterface(FcpInterface fcpInterface) { + this.fcpInterface = fcpInterface; + } + + /** * Returns the status of the given Sone. * * @param sone @@ -284,6 +311,26 @@ public class Core implements IdentityListener, UpdateListener { } /** + * Returns the Sone rescuer for the given local Sone. + * + * @param sone + * The local Sone to get the rescuer for + * @return The Sone rescuer for the given Sone + */ + public SoneRescuer getSoneRescuer(Sone sone) { + Validation.begin().isNotNull("Sone", sone).check().is("Local Sone", isLocalSone(sone)).check(); + synchronized (localSones) { + SoneRescuer soneRescuer = soneRescuers.get(sone); + if (soneRescuer == null) { + soneRescuer = new SoneRescuer(this, soneDownloader, sone); + soneRescuers.put(sone, soneRescuer); + soneRescuer.start(); + } + return soneRescuer; + } + } + + /** * Returns whether the given Sone is currently locked. * * @param sone @@ -334,6 +381,7 @@ public class Core implements IdentityListener, UpdateListener { * @return The Sone with the given ID, or {@code null} if there is no such * Sone */ + @Override public Sone getSone(String id, boolean create) { if (isLocalSone(id)) { return getLocalSone(id); @@ -556,6 +604,7 @@ public class Core implements IdentityListener, UpdateListener { * exists, {@code false} to return {@code null} * @return The post, or {@code null} if there is no such post */ + @Override public Post getPost(String postId, boolean create) { synchronized (posts) { Post post = posts.get(postId); @@ -836,33 +885,7 @@ public class Core implements IdentityListener, UpdateListener { soneInserters.put(sone, soneInserter); setSoneStatus(sone, SoneStatus.idle); loadSone(sone); - if (!preferences.isSoneRescueMode()) { - soneInserter.start(); - } - new Thread(new Runnable() { - - @Override - @SuppressWarnings("synthetic-access") - public void run() { - if (!preferences.isSoneRescueMode()) { - return; - } - logger.log(Level.INFO, "Trying to restore Sone from Freenet…"); - coreListenerManager.fireRescuingSone(sone); - lockSone(sone); - long edition = sone.getLatestEdition(); - while (!stopped && (edition >= 0) && preferences.isSoneRescueMode()) { - logger.log(Level.FINE, "Downloading edition " + edition + "…"); - soneDownloader.fetchSone(sone, sone.getRequestUri().setKeyType("SSK").setDocName("Sone-" + edition)); - --edition; - } - logger.log(Level.INFO, "Finished restoring Sone from Freenet, starting Inserter…"); - saveSone(sone); - coreListenerManager.fireRescuedSone(sone); - soneInserter.start(); - } - - }, "Sone Downloader").start(); + soneInserter.start(); return sone; } } @@ -917,6 +940,7 @@ public class Core implements IdentityListener, UpdateListener { for (Sone localSone : getLocalSones()) { if (localSone.getOptions().getBooleanOption("AutoFollow").get()) { localSone.addFriend(sone.getId()); + saveSone(localSone); } } } @@ -1029,14 +1053,28 @@ public class Core implements IdentityListener, UpdateListener { } /** - * Updates the stores Sone with the given Sone. + * Updates the stored Sone with the given Sone. * * @param sone * The updated Sone */ public void updateSone(Sone sone) { + updateSone(sone, false); + } + + /** + * Updates the stored Sone with the given Sone. If {@code soneRescueMode} is + * {@code true}, an older Sone than the current Sone can be given to restore + * an old state. + * + * @param sone + * The Sone to update + * @param soneRescueMode + * {@code true} if the stored Sone should be updated regardless + * of the age of the given Sone + */ + public void updateSone(Sone sone, boolean soneRescueMode) { if (hasSone(sone.getId())) { - 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 }); @@ -1473,7 +1511,7 @@ public class Core implements IdentityListener, UpdateListener { logger.log(Level.FINE, "Tried to create post for non-local Sone: %s", sone); return null; } - Post post = new Post(sone, time, text); + final Post post = new Post(sone, time, text); if (recipient != null) { post.setRecipient(recipient); } @@ -1486,6 +1524,16 @@ public class Core implements IdentityListener, UpdateListener { } sone.addPost(post); saveSone(sone); + localElementTicker.registerEvent(System.currentTimeMillis() + 10 * 1000, new Runnable() { + + /** + * {@inheritDoc} + */ + @Override + public void run() { + markPostKnown(post); + } + }, "Mark " + post + " read."); return post; } @@ -1606,7 +1654,7 @@ public class Core implements IdentityListener, UpdateListener { logger.log(Level.FINE, "Tried to create reply for non-local Sone: %s", sone); return null; } - Reply reply = new Reply(sone, post, System.currentTimeMillis(), text); + final Reply reply = new Reply(sone, post, System.currentTimeMillis(), text); synchronized (replies) { replies.put(reply.getId(), reply); } @@ -1616,6 +1664,16 @@ public class Core implements IdentityListener, UpdateListener { } sone.addReply(reply); saveSone(sone); + localElementTicker.registerEvent(System.currentTimeMillis() + 10 * 1000, new Runnable() { + + /** + * {@inheritDoc} + */ + @Override + public void run() { + markReplyKnown(reply); + } + }, "Mark " + reply + " read."); return reply; } @@ -1676,6 +1734,9 @@ public class Core implements IdentityListener, UpdateListener { for (SoneInserter soneInserter : soneInserters.values()) { soneInserter.stop(); } + for (Sone localSone : localSones.values()) { + saveSone(localSone); + } } updateChecker.stop(); updateChecker.removeUpdateListener(this); @@ -1701,10 +1762,13 @@ public class Core implements IdentityListener, UpdateListener { configuration.getIntValue("Option/ConfigurationVersion").setValue(0); configuration.getIntValue("Option/InsertionDelay").setValue(options.getIntegerOption("InsertionDelay").getReal()); configuration.getIntValue("Option/PostsPerPage").setValue(options.getIntegerOption("PostsPerPage").getReal()); + configuration.getIntValue("Option/CharactersPerPost").setValue(options.getIntegerOption("CharactersPerPost").getReal()); configuration.getBooleanValue("Option/RequireFullAccess").setValue(options.getBooleanOption("RequireFullAccess").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/ActivateFcpInterface").setValue(options.getBooleanOption("ActivateFcpInterface").getReal()); + configuration.getIntValue("Option/FcpFullAccessRequired").setValue(options.getIntegerOption("FcpFullAccessRequired").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()); @@ -1767,7 +1831,7 @@ public class Core implements IdentityListener, UpdateListener { @SuppressWarnings("unchecked") private void loadConfiguration() { /* create options. */ - options.addIntegerOption("InsertionDelay", new DefaultOption(60, new OptionWatcher() { + options.addIntegerOption("InsertionDelay", new DefaultOption(60, new IntegerRangeValidator(0, Integer.MAX_VALUE), new OptionWatcher() { @Override public void optionChanged(Option option, Integer oldValue, Integer newValue) { @@ -1775,11 +1839,29 @@ public class Core implements IdentityListener, UpdateListener { } })); - options.addIntegerOption("PostsPerPage", new DefaultOption(10)); + options.addIntegerOption("PostsPerPage", new DefaultOption(10, new IntegerRangeValidator(1, Integer.MAX_VALUE))); + options.addIntegerOption("CharactersPerPost", new DefaultOption(200, new OrValidator(new IntegerRangeValidator(50, Integer.MAX_VALUE), new EqualityValidator(-1)))); options.addBooleanOption("RequireFullAccess", new DefaultOption(false)); - options.addIntegerOption("PositiveTrust", new DefaultOption(75)); - options.addIntegerOption("NegativeTrust", new DefaultOption(-25)); + options.addIntegerOption("PositiveTrust", new DefaultOption(75, new IntegerRangeValidator(0, 100))); + options.addIntegerOption("NegativeTrust", new DefaultOption(-25, new IntegerRangeValidator(-100, 100))); options.addStringOption("TrustComment", new DefaultOption("Set from Sone Web Interface")); + options.addBooleanOption("ActivateFcpInterface", new DefaultOption(false, new OptionWatcher() { + + @Override + @SuppressWarnings("synthetic-access") + public void optionChanged(Option option, Boolean oldValue, Boolean newValue) { + fcpInterface.setActive(newValue); + } + })); + options.addIntegerOption("FcpFullAccessRequired", new DefaultOption(2, new OptionWatcher() { + + @Override + @SuppressWarnings("synthetic-access") + public void optionChanged(Option option, Integer oldValue, Integer newValue) { + fcpInterface.setFullAccessRequired(FullAccessRequired.values()[newValue]); + } + + })); options.addBooleanOption("SoneRescueMode", new DefaultOption(false)); options.addBooleanOption("ClearOnNextRestart", new DefaultOption(false)); options.addBooleanOption("ReallyClearOnNextRestart", new DefaultOption(false)); @@ -1795,12 +1877,15 @@ public class Core implements IdentityListener, UpdateListener { return; } - options.getIntegerOption("InsertionDelay").set(configuration.getIntValue("Option/InsertionDelay").getValue(null)); - options.getIntegerOption("PostsPerPage").set(configuration.getIntValue("Option/PostsPerPage").getValue(null)); + loadConfigurationValue("InsertionDelay"); + loadConfigurationValue("PostsPerPage"); + loadConfigurationValue("CharactersPerPost"); options.getBooleanOption("RequireFullAccess").set(configuration.getBooleanValue("Option/RequireFullAccess").getValue(null)); - options.getIntegerOption("PositiveTrust").set(configuration.getIntValue("Option/PositiveTrust").getValue(null)); - options.getIntegerOption("NegativeTrust").set(configuration.getIntValue("Option/NegativeTrust").getValue(null)); + loadConfigurationValue("PositiveTrust"); + loadConfigurationValue("NegativeTrust"); options.getStringOption("TrustComment").set(configuration.getStringValue("Option/TrustComment").getValue(null)); + options.getBooleanOption("ActivateFcpInterface").set(configuration.getBooleanValue("Option/ActivateFcpInterface").getValue(null)); + options.getIntegerOption("FcpFullAccessRequired").set(configuration.getIntValue("Option/FcpFullAccessRequired").getValue(null)); options.getBooleanOption("SoneRescueMode").set(configuration.getBooleanValue("Option/SoneRescueMode").getValue(null)); /* load known Sones. */ @@ -1854,6 +1939,21 @@ public class Core implements IdentityListener, UpdateListener { } /** + * Loads an {@link Integer} configuration value for the option with the + * given name, logging validation failures. + * + * @param optionName + * The name of the option to load + */ + private void loadConfigurationValue(String optionName) { + try { + options.getIntegerOption(optionName).set(configuration.getIntValue("Option/" + optionName).getValue(null)); + } catch (IllegalArgumentException iae1) { + logger.log(Level.WARNING, "Invalid value for " + optionName + " in configuration, using default."); + } + } + + /** * Generate a Sone URI from the given URI and latest edition. * * @param uriString @@ -1971,6 +2071,7 @@ public class Core implements IdentityListener, UpdateListener { } synchronized (newSones) { newSones.remove(identity.getId()); + coreListenerManager.fireSoneRemoved(sone); } } @@ -2017,6 +2118,18 @@ public class Core implements IdentityListener, UpdateListener { } /** + * Validates the given insertion delay. + * + * @param insertionDelay + * The insertion delay to validate + * @return {@code true} if the given insertion delay was valid, {@code + * false} otherwise + */ + public boolean validateInsertionDelay(Integer insertionDelay) { + return options.getIntegerOption("InsertionDelay").validate(insertionDelay); + } + + /** * Sets the insertion delay * * @param insertionDelay @@ -2039,6 +2152,18 @@ public class Core implements IdentityListener, UpdateListener { } /** + * Validates the number of posts per page. + * + * @param postsPerPage + * The number of posts per page + * @return {@code true} if the number of posts per page was valid, + * {@code false} otherwise + */ + public boolean validatePostsPerPage(Integer postsPerPage) { + return options.getIntegerOption("PostsPerPage").validate(postsPerPage); + } + + /** * Sets the number of posts to show per page. * * @param postsPerPage @@ -2051,6 +2176,41 @@ public class Core implements IdentityListener, UpdateListener { } /** + * Returns the number of characters per post, or -1 if the + * posts should not be cut off. + * + * @return The numbers of characters per post + */ + public int getCharactersPerPost() { + return options.getIntegerOption("CharactersPerPost").get(); + } + + /** + * Validates the number of characters per post. + * + * @param charactersPerPost + * The number of characters per post + * @return {@code true} if the number of characters per post was valid, + * {@code false} otherwise + */ + public boolean validateCharactersPerPost(Integer charactersPerPost) { + return options.getIntegerOption("CharactersPerPost").validate(charactersPerPost); + } + + /** + * Sets the number of characters per post. + * + * @param charactersPerPost + * The number of characters per post, or -1 to + * not cut off the posts + * @return This preferences objects + */ + public Preferences setCharactersPerPost(Integer charactersPerPost) { + options.getIntegerOption("CharactersPerPost").set(charactersPerPost); + return this; + } + + /** * Returns whether Sone requires full access to be even visible. * * @return {@code true} if Sone requires full access, {@code false} @@ -2081,6 +2241,18 @@ public class Core implements IdentityListener, UpdateListener { } /** + * Validates the positive trust. + * + * @param positiveTrust + * The positive trust to validate + * @return {@code true} if the positive trust was valid, {@code false} + * otherwise + */ + public boolean validatePositiveTrust(Integer positiveTrust) { + return options.getIntegerOption("PositiveTrust").validate(positiveTrust); + } + + /** * Sets the positive trust. * * @param positiveTrust @@ -2103,6 +2275,18 @@ public class Core implements IdentityListener, UpdateListener { } /** + * Validates the negative trust. + * + * @param negativeTrust + * The negative trust to validate + * @return {@code true} if the negative trust was valid, {@code false} + * otherwise + */ + public boolean validateNegativeTrust(Integer negativeTrust) { + return options.getIntegerOption("NegativeTrust").validate(negativeTrust); + } + + /** * Sets the negative trust. * * @param negativeTrust @@ -2139,25 +2323,53 @@ public class Core implements IdentityListener, UpdateListener { } /** - * Returns whether the rescue mode is active. + * Returns whether the {@link FcpInterface FCP interface} is currently + * active. * - * @return {@code true} if the rescue mode is active, {@code false} - * otherwise + * @see FcpInterface#setActive(boolean) + * @return {@code true} if the FCP interface is currently active, + * {@code false} otherwise */ - public boolean isSoneRescueMode() { - return options.getBooleanOption("SoneRescueMode").get(); + public boolean isFcpInterfaceActive() { + return options.getBooleanOption("ActivateFcpInterface").get(); } /** - * Sets whether the rescue mode is active. + * Sets whether the {@link FcpInterface FCP interface} is currently + * active. * - * @param soneRescueMode - * {@code true} if the rescue mode is active, {@code false} - * otherwise + * @see FcpInterface#setActive(boolean) + * @param fcpInterfaceActive + * {@code true} to activate the FCP interface, {@code false} + * to deactivate the FCP interface + * @return This preferences object + */ + public Preferences setFcpInterfaceActive(boolean fcpInterfaceActive) { + options.getBooleanOption("ActivateFcpInterface").set(fcpInterfaceActive); + return this; + } + + /** + * Returns the action level for which full access to the FCP interface + * is required. + * + * @return The action level for which full access to the FCP interface + * is required + */ + public FullAccessRequired getFcpFullAccessRequired() { + return FullAccessRequired.values()[options.getIntegerOption("FcpFullAccessRequired").get()]; + } + + /** + * Sets the action level for which full access to the FCP interface is + * required + * + * @param fcpFullAccessRequired + * The action level * @return This preferences */ - public Preferences setSoneRescueMode(Boolean soneRescueMode) { - options.getBooleanOption("SoneRescueMode").set(soneRescueMode); + public Preferences setFcpFullAccessRequired(FullAccessRequired fcpFullAccessRequired) { + options.getIntegerOption("FcpFullAccessRequired").set((fcpFullAccessRequired != null) ? fcpFullAccessRequired.ordinal() : null); return this; }