--- /dev/null
+# Sone – The Social Network Plugin for Freenet
+
+Sone aims to provide social network functionality for [Freenet](https://freenetproject.org/) (also here [on GitHub](https://github.com/freenet/)).
+
+## Installing
+
+### Prerequisites
+
+For Sone to work you will need a running Freenet node, of course. You will also need the Web of Trust plugin from the official plugins listed on your node’s plugin manager page (*Configuration → Plugins* in the menu).
+
+If you already have a web of trust identity, you can skip the next section.
+
+You will also need to create an identity in the web of trust. Select *Community* from the menu, choose “Generate” and follow the instructions on-screen until your identity has been created.
+
+### Loading/Installing
+
+For Sone to work you will need a running Freenet node and the WebOfTrust plugin. Loading Sone happens from Freenet’s web interface using the “Add an Unofficial Plugin from Freenet” section from the node’s plugin manager at *Configuration → Plugins*, at the bottom of the page. Enter the key of the plugin (starting with “USK@”) into the text field and press the “Load” button. The plugin should then be downloaded from Freenet and started once it’s ready.
+
+The node will remember which plugins you loaded so that you don’t need to do that again after your node restarts (e.g. for updates).
+
+## Basic Usage
+
+### Creating a Sone
+
+Now when you select *Sone* from the menu you will be faced with the information that you do not have any Sones yet. Sone does offer you to create a Sone for your web of trust identity, though. Pick the identity you want to use in Sone and hit “Create Sone.”
+
+### Configuring Sone
+
+Choose *Sone → Options* from the menu and see through the list of options. You might want to to activate the “automatically follow new Sones” options so that your post feed does not look so empty. You can also choose to be notified for new Sones, posts, and replies.
+
+In Sone you have the ability to upload a custom avatar for your identity. However, you can not force other people to see it; see the “Avatar Options” section for how the avatar display is controlled.
+
+### Writing Posts
+
+If you choose *Sone* from the menu or simply click the large avatar on the top of the page you will be taken to the “Post Feed” page. It lists your posts as well as posts of all Sones that you follow. You can also write your own posts from here: just click inside the text field at the top of the page, it will expand into a larger box that you can write your post in. (It is also resizable and does not place a limit on the amount of text you want to write.) When you are done, hit the “Post” button right next to it. Your post will then appear at the top of your post feed.
+
+### Replying to Posts
+
+Now, a social network wouldn’t be much fun if you couldn’t talk with other people, and the easiest way to get in touch with them is to reply to one of their posts. To do so, just press the “Comment” link below a post. A text field will appear in which you can simply enter your text. You can press the “+” button on the left side to choose a different identity to post with (in case you have more than one), and once you’re done you hit the “Post Reply” button. The reply will then appear below the post you replied to.
+
+### Liking Posts and Replies
+
+“Liking” is quite the mysterious function. It doesn’t necessarily mean that you literally like something someone said, it is more of a general approval, or a “me too.” That being said, pressing this button has no other consequences than showing other people that you did press this button.
+
+## Advanced Topics
+
+### Addings Links to Posts/Replies
+
+When displaying posts and replies, Sone first parses the text. Special elements, such as Freenet URIs and Sone elements with a special syntax, are replaced with formatting that allow your browser to navigate the elements. Sone recognizes the following elements:
+
+* Links to Freenet URIs are linked to as-is. Make sure to separate the URI from surrounding text by whitespace, such as space or line breaks.
+* Links to other Sone’s profiles are added by the prefix “sone://” followed by the ID of the Sone. It is also possible to get the link for a Sone from a post or reply by that Sone; just copy the URL behind the “[link author]” link.
+* Links to other posts are added by the prefix “post://” followed by the ID of the post. You can also find the post ID behind the “[link post]” link below a post.
<modelVersion>4.0.0</modelVersion>
<groupId>net.pterodactylus</groupId>
<artifactId>sone</artifactId>
- <version>0.9.2</version>
+ <version>0.9.3</version>
<dependencies>
<dependency>
<groupId>net.pterodactylus</groupId>
public class Core extends AbstractService implements SoneProvider, PostProvider, PostReplyProvider {
/** The logger. */
- private static final Logger logger = getLogger("Sone.Core");
+ private static final Logger logger = getLogger(Core.class.getName());
/** The start time. */
private final long startupTime = System.currentTimeMillis();
soneInserters.put(sone, soneInserter);
}
loadSone(sone);
+ database.storeSone(sone);
sone.setStatus(SoneStatus.idle);
soneInserter.start();
return sone;
logger.log(Level.WARNING, "Given Identity is null!");
return null;
}
- final Long latestEdition = tryParse(fromNullable(
- identity.getProperty("Sone.LatestEdition")).or("0"));
+ String property = fromNullable(identity.getProperty("Sone.LatestEdition")).or("0");
+ long latestEdition = fromNullable(tryParse(property)).or(0L);
Optional<Sone> existingSone = getSone(identity.getId());
if (existingSone.isPresent() && existingSone.get().isLocal()) {
return existingSone.get();
}
/* load options. */
- sone.getOptions().setAutoFollow(configuration.getBooleanValue(sonePrefix + "/Options/AutoFollow").getValue(null));
- sone.getOptions().setSoneInsertNotificationEnabled(configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").getValue(null));
- sone.getOptions().setShowNewSoneNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewSones").getValue(null));
- sone.getOptions().setShowNewPostNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").getValue(null));
- sone.getOptions().setShowNewReplyNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").getValue(null));
+ sone.getOptions().setAutoFollow(configuration.getBooleanValue(sonePrefix + "/Options/AutoFollow").getValue(false));
+ sone.getOptions().setSoneInsertNotificationEnabled(configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").getValue(false));
+ sone.getOptions().setShowNewSoneNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewSones").getValue(true));
+ sone.getOptions().setShowNewPostNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").getValue(true));
+ sone.getOptions().setShowNewReplyNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").getValue(true));
sone.getOptions().setShowCustomAvatars(ShowCustomAvatars.valueOf(configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars").getValue(ShowCustomAvatars.NEVER.name())));
/* if we’re still here, Sone was loaded successfully. */
for (Album album : topLevelAlbums) {
sone.getRootAlbum().addAlbum(album);
}
- database.storeSone(sone);
synchronized (soneInserters) {
soneInserters.get(sone).setLastInsertFingerprint(lastInsertFingerprint);
}
public class FreenetInterface {
/** The logger. */
- private static final Logger logger = getLogger("Sone.FreenetInterface");
+ private static final Logger logger = getLogger(FreenetInterface.class.getName());
/** The event bus. */
private final EventBus eventBus;
public class ImageInserter {
/** The logger. */
- private static final Logger logger = getLogger("Sone.Image.Inserter");
+ private static final Logger logger = getLogger(ImageInserter.class.getName());
/** The freenet interface. */
private final FreenetInterface freenetInterface;
public class SoneDownloaderImpl extends AbstractService implements SoneDownloader {
/** The logger. */
- private static final Logger logger = getLogger("Sone.Downloader");
+ private static final Logger logger = getLogger(SoneDownloaderImpl.class.getName());
/** The maximum protocol version. */
private static final int MAX_PROTOCOL_VERSION = 0;
public class SoneInserter extends AbstractService {
/** The logger. */
- private static final Logger logger = getLogger("Sone.Inserter");
+ private static final Logger logger = getLogger(SoneInserter.class.getName());
/** The insertion delay (in seconds). */
private static final AtomicInteger insertionDelay = new AtomicInteger(60);
* @return The fingerprint of the last insert
*/
public String getLastInsertFingerprint() {
- return soneModificationDetector.getOriginalFingerprint();
+ return soneModificationDetector.getLastInsertFingerprint();
}
/**
private final LockableFingerprintProvider lockableFingerprintProvider;
private final AtomicInteger insertionDelay;
private Optional<Long> lastModificationTime;
- private String originalFingerprint;
- private String lastFingerprint;
+ private String lastInsertFingerprint;
+ private String lastCheckFingerprint;
SoneModificationDetector(LockableFingerprintProvider lockableFingerprintProvider, AtomicInteger insertionDelay) {
this(systemTicker(), lockableFingerprintProvider, insertionDelay);
this.ticker = ticker;
this.lockableFingerprintProvider = lockableFingerprintProvider;
this.insertionDelay = insertionDelay;
- lastFingerprint = originalFingerprint;
+ lastCheckFingerprint = lastInsertFingerprint;
}
public boolean isEligibleForInsert() {
if (lockableFingerprintProvider.isLocked()) {
lastModificationTime = absent();
- lastFingerprint = "";
+ lastCheckFingerprint = "";
return false;
}
String fingerprint = lockableFingerprintProvider.getFingerprint();
- if (originalFingerprint.equals(fingerprint)) {
+ if (fingerprint.equals(lastInsertFingerprint)) {
lastModificationTime = absent();
- lastFingerprint = fingerprint;
+ lastCheckFingerprint = fingerprint;
return false;
}
- if (!lastFingerprint.equals(fingerprint)) {
+ if (!Objects.equal(lastCheckFingerprint, fingerprint)) {
lastModificationTime = of(ticker.read());
- lastFingerprint = fingerprint;
+ lastCheckFingerprint = fingerprint;
return false;
}
return insertionDelayHasPassed();
}
- public String getOriginalFingerprint() {
- return originalFingerprint;
+ public String getLastInsertFingerprint() {
+ return lastInsertFingerprint;
}
public void setFingerprint(String fingerprint) {
- originalFingerprint = fingerprint;
- lastFingerprint = originalFingerprint;
+ lastInsertFingerprint = fingerprint;
+ lastCheckFingerprint = lastInsertFingerprint;
lastModificationTime = absent();
}
}
public boolean isModified() {
- return !Objects.equal(lockableFingerprintProvider.getFingerprint(), originalFingerprint);
+ return !Objects.equal(lockableFingerprintProvider.getFingerprint(), lastInsertFingerprint);
}
/**
*/
public class SoneParser {
- private static final Logger logger = getLogger("Sone.Parser");
+ private static final Logger logger = getLogger(SoneParser.class.getName());
private static final int MAX_PROTOCOL_VERSION = 0;
private final Core core;
public class SoneUri {
/** The logger. */
- private static final Logger logger = getLogger("Sone.Data");
+ private static final Logger logger = getLogger(SoneUri.class.getName());
/**
* Generate a Sone URI from the given URI.
public class UpdateChecker {
/** The logger. */
- private static final Logger logger = getLogger("Sone.UpdateChecker");
+ private static final Logger logger = getLogger(UpdateChecker.class.getName());
/** The event bus. */
private final EventBus eventBus;
public class WebOfTrustUpdaterImpl extends AbstractService implements WebOfTrustUpdater {
/** The logger. */
- private static final Logger logger = getLogger("Sone.WoT.Updater");
+ private static final Logger logger = getLogger(WebOfTrustUpdaterImpl.class.getName());
/** Stop job. */
@SuppressWarnings("synthetic-access")
import static com.google.common.base.Objects.equal;
+import com.google.common.base.Objects;
+
/**
* Container for the client information of a {@link Sone}.
*
return equal(getName(), client.getName()) && equal(getVersion(), client.getVersion());
}
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(name, version);
+ }
+
}
}
};
- public static final Function<Sone, String> toSoneXmlUri =
- new Function<Sone, String>() {
- @Nonnull
- @Override
- public String apply(@Nullable Sone input) {
- return input.getRequestUri()
- .setMetaString(new String[] { "sone.xml" })
- .toString();
- }
- };
-
public static final Function<Sone, List<Album>> toAllAlbums = new Function<Sone, List<Album>>() {
@Override
public List<Album> apply(@Nullable Sone sone) {
checkArgument(equals(album.getParent()), "album must belong to this album");
int oldIndex = albums.indexOf(album);
if (oldIndex <= 0) {
- return null;
+ return album;
}
albums.remove(oldIndex);
albums.add(oldIndex - 1, album);
checkArgument(equals(album.getParent()), "album must belong to this album");
int oldIndex = albums.indexOf(album);
if ((oldIndex < 0) || (oldIndex >= (albums.size() - 1))) {
- return null;
+ return album;
}
albums.remove(oldIndex);
albums.add(oldIndex + 1, album);
checkArgument(image.getAlbum().equals(this), "image must belong to this album");
int oldIndex = imageIds.indexOf(image.getId());
if (oldIndex <= 0) {
- return null;
+ return image;
}
imageIds.remove(image.getId());
imageIds.add(oldIndex - 1, image.getId());
checkArgument(image.getAlbum().equals(this), "image must belong to this album");
int oldIndex = imageIds.indexOf(image.getId());
if ((oldIndex == -1) || (oldIndex >= (imageIds.size() - 1))) {
- return null;
+ return image;
}
imageIds.remove(image.getId());
imageIds.add(oldIndex + 1, image.getId());
import freenet.keys.FreenetURI;
+import com.google.common.base.Objects;
+
/**
* {@link Sone} implementation that only stores the ID of a Sone and returns
* {@code null}, {@code 0}, or empty collections where appropriate.
return id;
}
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ return (object != null) && (object.getClass() == getClass()) && Objects.equal(id, ((IdOnlySone) object).id);
+ }
+
}
public class SoneImpl implements Sone {
/** The logger. */
- private static final Logger logger = getLogger("Sone.Data");
+ private static final Logger logger = getLogger(SoneImpl.class.getName());
/** The database. */
private final Database database;
*/
public class ConfigurationLoader {
- private static final Logger logger =
- Logger.getLogger("Sone.Database.Memory.Configuration");
+ private static final Logger logger = Logger.getLogger(ConfigurationLoader.class.getName());
private final Configuration configuration;
public ConfigurationLoader(Configuration configuration) {
}
/** The logger. */
- private static final Logger logger = getLogger("Sone.External.Fcp");
+ private static final Logger logger = getLogger(FcpInterface.class.getName());
/** Whether the FCP interface is currently active. */
private final AtomicBoolean active = new AtomicBoolean();
/** The logger. */
@SuppressWarnings("unused")
- private static final Logger logger = getLogger("Sone.Fred");
+ private static final Logger logger = getLogger(PluginStoreConfigurationBackend.class.getName());
/** The plugin respirator. */
private final PluginRespirator pluginRespirator;
return new Predicate<Identity>() {
@Override
public boolean apply(Identity identity) {
- return identities.containsKey(identity.getId());
+ return (identity != null) && identities.containsKey(identity.getId());
}
};
}
public class IdentityManagerImpl extends AbstractService implements IdentityManager {
/** The logger. */
- private static final Logger logger = getLogger("Sone.Identities");
+ private static final Logger logger = getLogger(IdentityManagerImpl.class.getName());
/** The event bus. */
private final EventBus eventBus;
import static com.google.common.base.Objects.equal;
+import com.google.common.base.Objects;
+
/**
* Container class for trust in the web of trust.
*
return equal(getExplicit(), trust.getExplicit()) && equal(getImplicit(), trust.getImplicit()) && equal(getDistance(), trust.getDistance());
}
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(explicit, implicit, distance);
+ }
+
/** {@inheritDoc} */
@Override
public String toString() {
public class WebOfTrustConnector {
/** The logger. */
- private static final Logger logger = getLogger("Sone.WoT.Connector");
+ private static final Logger logger = getLogger(WebOfTrustConnector.class.getName());
/** The name of the WoT plugin. */
private static final String WOT_PLUGIN_NAME = "plugins.WebOfTrust.WebOfTrust";
* if an error occured talking to the Web of Trust plugin
*/
public Set<Identity> loadTrustedIdentities(OwnIdentity ownIdentity) throws PluginException {
- return loadTrustedIdentities(ownIdentity, null);
+ return loadTrustedIdentities(ownIdentity, Optional.<String>absent());
}
/**
import java.util.logging.Logger;
import net.pterodactylus.sone.core.Core;
-import net.pterodactylus.sone.core.FreenetInterface;
-import net.pterodactylus.sone.core.WebOfTrustUpdater;
-import net.pterodactylus.sone.core.WebOfTrustUpdaterImpl;
-import net.pterodactylus.sone.database.Database;
-import net.pterodactylus.sone.database.PostBuilderFactory;
-import net.pterodactylus.sone.database.PostProvider;
-import net.pterodactylus.sone.database.PostReplyBuilderFactory;
-import net.pterodactylus.sone.database.SoneProvider;
-import net.pterodactylus.sone.database.memory.MemoryDatabase;
import net.pterodactylus.sone.fcp.FcpInterface;
import net.pterodactylus.sone.freenet.PluginStoreConfigurationBackend;
-import net.pterodactylus.sone.freenet.plugin.PluginConnector;
import net.pterodactylus.sone.freenet.wot.Context;
-import net.pterodactylus.sone.freenet.wot.IdentityManager;
-import net.pterodactylus.sone.freenet.wot.IdentityManagerImpl;
import net.pterodactylus.sone.freenet.wot.WebOfTrustConnector;
import net.pterodactylus.sone.web.WebInterface;
import net.pterodactylus.util.config.Configuration;
import net.pterodactylus.util.version.Version;
import com.google.common.base.Optional;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
import com.google.common.eventbus.EventBus;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
-import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.matcher.Matchers;
import com.google.inject.spi.InjectionListener;
*/
public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, FredPluginBaseL10n, FredPluginThreadless, FredPluginVersioned {
+ private static final Logger soneLogger = getLogger("net.pterodactylus.sone");
+
static {
/* initialize logging. */
- Logger soneLogger = getLogger("Sone");
soneLogger.setUseParentHandlers(false);
soneLogger.addHandler(new Handler() {
+ private final LoadingCache<String, Class<?>> classCache = CacheBuilder.newBuilder()
+ .build(new CacheLoader<String, Class<?>>() {
+ @Override
+ public Class<?> load(String key) throws Exception {
+ return Class.forName(key);
+ }
+ });
+
@Override
public void publish(LogRecord logRecord) {
int recordLevel = logRecord.getLevel().intValue();
+ Class<?> loggingClass = classCache.getUnchecked(logRecord.getLoggerName());
if (recordLevel < Level.FINE.intValue()) {
- freenet.support.Logger.debug(logRecord.getLoggerName(), logRecord.getMessage(), logRecord.getThrown());
+ freenet.support.Logger.debug(loggingClass, logRecord.getMessage(), logRecord.getThrown());
} else if (recordLevel < Level.INFO.intValue()) {
- freenet.support.Logger.minor(logRecord.getLoggerName(), logRecord.getMessage(), logRecord.getThrown());
+ freenet.support.Logger.minor(loggingClass, logRecord.getMessage(), logRecord.getThrown());
} else if (recordLevel < Level.WARNING.intValue()) {
- freenet.support.Logger.normal(logRecord.getLoggerName(), logRecord.getMessage(), logRecord.getThrown());
+ freenet.support.Logger.normal(loggingClass, logRecord.getMessage(), logRecord.getThrown());
} else if (recordLevel < Level.SEVERE.intValue()) {
- freenet.support.Logger.warning(logRecord.getLoggerName(), logRecord.getMessage(), logRecord.getThrown());
+ freenet.support.Logger.warning(loggingClass, logRecord.getMessage(), logRecord.getThrown());
} else {
- freenet.support.Logger.error(logRecord.getLoggerName(), logRecord.getMessage(), logRecord.getThrown());
+ freenet.support.Logger.error(loggingClass, logRecord.getMessage(), logRecord.getThrown());
}
}
}
/** The version. */
- public static final Version VERSION = new Version(0, 9, 2);
+ public static final Version VERSION = new Version(0, 9, 3);
/** The current year at time of release. */
private static final int YEAR = 2015;
private static final String SONE_HOMEPAGE = "USK@nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI,DuQSUZiI~agF8c-6tjsFFGuZ8eICrzWCILB60nT8KKo,AQACAAE/sone/";
- private static final int LATEST_EDITION = 69;
+ private static final int LATEST_EDITION = 70;
/** The logger. */
- private static final Logger logger = getLogger("Sone.Plugin");
+ private static final Logger logger = getLogger(SonePlugin.class.getName());
/** The plugin respirator. */
private PluginRespirator pluginRespirator;
*/
@Override
public void terminate() {
+ deregisterLoggerHandlers();
try {
/* stop the web interface. */
webInterface.stop();
}
}
+ private void deregisterLoggerHandlers() {
+ for (Handler handler : soneLogger.getHandlers()) {
+ soneLogger.removeHandler(handler);
+ }
+ }
+
//
// INTERFACE FredPluginFCP
//
public class SoneAccessor extends ReflectionAccessor {
/** The logger. */
- private static final Logger logger = getLogger("Sone.Data");
+ private static final Logger logger = getLogger(SoneAccessor.class.getName());
/** The core. */
private final Core core;
public class SoneTextParser implements Parser<SoneTextParserContext> {
/** The logger. */
- private static final Logger logger = getLogger("Sone.Data.Parser");
+ private static final Logger logger = getLogger(SoneTextParser.class.getName());
/** Pattern to detect whitespace. */
private static final Pattern whitespacePattern = Pattern.compile("[\\u000a\u0020\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u200c\u200d\u202f\u205f\u2060\u2800\u3000]");
*/
private enum LinkType {
- /** Link is a KSK. */
- KSK("KSK@"),
+ KSK("KSK@", true),
+ CHK("CHK@", true),
+ SSK("SSK@", true),
+ USK("USK@", true),
+ HTTP("http://", false),
+ HTTPS("https://", false),
+ SONE("sone://", false),
+ POST("post://", false);
- /** Link is a CHK. */
- CHK("CHK@"),
-
- /** Link is an SSK. */
- SSK("SSK@"),
-
- /** Link is a USK. */
- USK("USK@"),
-
- /** Link is HTTP. */
- HTTP("http://"),
-
- /** Link is HTTPS. */
- HTTPS("https://"),
-
- /** Link is a Sone. */
- SONE("sone://"),
-
- /** Link is a post. */
- POST("post://");
-
- /** The scheme identifying this link type. */
private final String scheme;
+ private final boolean freenetLink;
- /**
- * Creates a new link type identified by the given scheme.
- *
- * @param scheme
- * The scheme of the link type
- */
- private LinkType(String scheme) {
+ LinkType(String scheme, boolean freenetLink) {
this.scheme = scheme;
+ this.freenetLink = freenetLink;
}
/**
return scheme;
}
+ public boolean isFreenetLink() {
+ return freenetLink;
+ }
+
}
/** The Sone provider. */
*/
boolean lineComplete = true;
while (line.length() > 0) {
- int nextKsk = line.indexOf("KSK@");
- int nextChk = line.indexOf("CHK@");
- int nextSsk = line.indexOf("SSK@");
- int nextUsk = line.indexOf("USK@");
- int nextHttp = line.indexOf("http://");
- int nextHttps = line.indexOf("https://");
- int nextSone = line.indexOf("sone://");
- int nextPost = line.indexOf("post://");
- if ((nextKsk == -1) && (nextChk == -1) && (nextSsk == -1) && (nextUsk == -1) && (nextHttp == -1) && (nextHttps == -1) && (nextSone == -1) && (nextPost == -1)) {
+ Optional<NextLink> nextLink = NextLink.findNextLink(line);
+ if (!nextLink.isPresent()) {
if (lineComplete && !lastLineEmpty) {
parts.add(new PlainTextPart("\n" + line));
} else {
}
break;
}
- int next = Integer.MAX_VALUE;
- LinkType linkType = null;
- if ((nextKsk > -1) && (nextKsk < next)) {
- next = nextKsk;
- linkType = LinkType.KSK;
- }
- if ((nextChk > -1) && (nextChk < next)) {
- next = nextChk;
- linkType = LinkType.CHK;
- }
- if ((nextSsk > -1) && (nextSsk < next)) {
- next = nextSsk;
- linkType = LinkType.SSK;
- }
- if ((nextUsk > -1) && (nextUsk < next)) {
- next = nextUsk;
- linkType = LinkType.USK;
- }
- if ((nextHttp > -1) && (nextHttp < next)) {
- next = nextHttp;
- linkType = LinkType.HTTP;
- }
- if ((nextHttps > -1) && (nextHttps < next)) {
- next = nextHttps;
- linkType = LinkType.HTTPS;
- }
- if ((nextSone > -1) && (nextSone < next)) {
- next = nextSone;
- linkType = LinkType.SONE;
- }
- if ((nextPost > -1) && (nextPost < next)) {
- next = nextPost;
- linkType = LinkType.POST;
- }
+ LinkType linkType = nextLink.get().getLinkType();
+ int next = nextLink.get().getPosition();
/* cut off “freenet:” from before keys. */
- if (((linkType == LinkType.KSK) || (linkType == LinkType.CHK) || (linkType == LinkType.SSK) || (linkType == LinkType.USK)) && (next >= 8) && (line.substring(next - 8, next).equals("freenet:"))) {
+ if (linkType.isFreenetLink() && (next >= 8) && (line.substring(next - 8, next).equals("freenet:"))) {
next -= 8;
line = line.substring(0, next) + line.substring(next + 8);
}
if (next > 0) {
parts.add(new PlainTextPart(line.substring(0, next)));
line = line.substring(next);
- next = 0;
}
lineComplete = false;
String link = line.substring(0, nextSpace);
String name = link;
logger.log(Level.FINER, String.format("Found link: %s", link));
- logger.log(Level.FINEST, String.format("CHK: %d, SSK: %d, USK: %d", nextChk, nextSsk, nextUsk));
/* if there is no text after the scheme, it’s not a link! */
if (link.equals(linkType.getScheme())) {
continue;
}
- if ((linkType == LinkType.KSK) || (linkType == LinkType.CHK) || (linkType == LinkType.SSK) || (linkType == LinkType.USK)) {
+ if (linkType.isFreenetLink()) {
FreenetURI uri;
if (name.indexOf('?') > -1) {
name = name.substring(0, name.indexOf('?'));
}
for (int partIndex = parts.size() - 1; partIndex >= 0; --partIndex) {
Part part = parts.getPart(partIndex);
- if (!(part instanceof PlainTextPart) || !"\n".equals(((PlainTextPart) part).getText())) {
+ if (!(part instanceof PlainTextPart) || !"\n".equals(part.getText())) {
break;
}
parts.removePart(partIndex);
return parts;
}
+ private static class NextLink {
+
+ private final int position;
+ private final LinkType linkType;
+
+ private NextLink(int position, LinkType linkType) {
+ this.position = position;
+ this.linkType = linkType;
+ }
+
+ public int getPosition() {
+ return position;
+ }
+
+ public LinkType getLinkType() {
+ return linkType;
+ }
+
+ public static Optional<NextLink> findNextLink(String line) {
+ int earliestLinkPosition = Integer.MAX_VALUE;
+ LinkType linkType = null;
+ for (LinkType possibleLinkType : LinkType.values()) {
+ int nextLinkPosition = line.indexOf(possibleLinkType.getScheme());
+ if (nextLinkPosition > -1) {
+ if (nextLinkPosition < earliestLinkPosition) {
+ earliestLinkPosition = nextLinkPosition;
+ linkType = possibleLinkType;
+ }
+ }
+ }
+ return earliestLinkPosition < Integer.MAX_VALUE ?
+ Optional.of(new NextLink(earliestLinkPosition, linkType)) : Optional.<NextLink>absent();
+ }
+
+ }
+
}
+++ /dev/null
-package net.pterodactylus.sone.utils;
-
-import com.google.common.base.Function;
-import com.google.common.base.Optional;
-import com.google.common.base.Predicate;
-
-/**
- * Helper methods for dealing with {@link Optional}s.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class Optionals {
-
- public static Predicate<Optional<?>> isPresent() {
- return new Predicate<Optional<?>>() {
- @Override
- public boolean apply(Optional<?> input) {
- return input.isPresent();
- }
- };
- }
-
- public static <T> Function<Optional<T>, T> get() {
- return new Function<Optional<T>, T>() {
- @Override
- public T apply(Optional<T> input) {
- return input.get();
- }
- };
- }
-
-}
public class CreateSonePage extends SoneTemplatePage {
/** The logger. */
- private static final Logger logger = getLogger("Sone.Web.CreateSone");
+ private static final Logger logger = getLogger(CreateSonePage.class.getName());
/**
* Creates a new “create Sone” page.
/** The logger. */
@SuppressWarnings("unused")
- private static final Logger logger = getLogger("Sone.Web.Login");
+ private static final Logger logger = getLogger(LoginPage.class.getName());
/**
* Creates a new login page.
public class SearchPage extends SoneTemplatePage {
/** The logger. */
- private static final Logger logger = getLogger("Sone.Web.Search");
+ private static final Logger logger = getLogger(SearchPage.class.getName());
/** Short-term cache. */
private final LoadingCache<List<Phrase>, Set<Hit<Post>>> hitCache = CacheBuilder.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).build(new CacheLoader<List<Phrase>, Set<Hit<Post>>>() {
package net.pterodactylus.sone.web;
+import static com.google.common.base.Optional.fromNullable;
import static java.util.logging.Logger.getLogger;
import java.awt.Image;
*/
public class UploadImagePage extends SoneTemplatePage {
- /** The logger. */
- private static final Logger logger = getLogger("Sone.Web.UploadImage");
+ private static final Logger logger = getLogger(UploadImagePage.class.getName());
+ private static final String UNKNOWN_MIME_TYPE = "application/octet-stream";
/**
* Creates a new “upload image” page.
ImageInputStream imageInputStream = ImageIO.createImageInputStream(imageDataInputStream);
Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(imageInputStream);
if (imageReaders.hasNext()) {
- return imageReaders.next().getOriginatingProvider().getMIMETypes()[0];
+ return fromNullable(imageReaders.next().getOriginatingProvider().getMIMETypes())
+ .or(new String[] { UNKNOWN_MIME_TYPE })[0];
}
} catch (IOException ioe1) {
logger.log(Level.FINE, "Could not detect MIME type for image.", ioe1);
}
- return "application/octet-stream";
+ return UNKNOWN_MIME_TYPE;
}
}
public class WebInterface {
/** The logger. */
- private static final Logger logger = getLogger("Sone.Web.Main");
+ private static final Logger logger = getLogger(WebInterface.class.getName());
/** The notification manager. */
private final NotificationManager notificationManager = new NotificationManager();
public abstract class JsonPage implements FreenetPage {
/** The logger. */
- private static final Logger logger = getLogger("Sone.Web.Ajax");
+ private static final Logger logger = getLogger(JsonPage.class.getName());
/** The JSON serializer. */
private static final ObjectMapper objectMapper = new ObjectMapper();
public class FreenetTemplatePage implements FreenetPage, LinkEnabledCallback {
/** The logger. */
- private static final Logger logger = getLogger("Sone.Web.Freenet");
+ private static final Logger logger = getLogger(FreenetTemplatePage.class.getName());
/** The path of the page. */
private final String path;
import java.util.concurrent.atomic.AtomicInteger;
import net.pterodactylus.sone.core.SoneModificationDetector.LockableFingerprintProvider;
-import net.pterodactylus.sone.data.Sone;
import com.google.common.base.Ticker;
import org.junit.Before;
@Test
public void originalFingerprintIsRetained() {
- assertThat(soneModificationDetector.getOriginalFingerprint(), is("original"));
+ assertThat(soneModificationDetector.getLastInsertFingerprint(), is("original"));
}
@Test
assertThat(soneModificationDetector.isEligibleForInsert(), is(false));
}
+ @Test
+ public void soneWithoutOriginalFingerprintIsNotEligibleAfter59Seconds() {
+ SoneModificationDetector soneModificationDetector = createDetectorWithoutOriginalFingerprint();
+ assertThat(soneModificationDetector.isEligibleForInsert(), is(false));
+ passTime(59);
+ assertThat(soneModificationDetector.isEligibleForInsert(), is(false));
+ }
+
+ private SoneModificationDetector createDetectorWithoutOriginalFingerprint() {
+ return new SoneModificationDetector(ticker, new LockableFingerprintProvider() {
+ @Override
+ public boolean isLocked() {
+ return false;
+ }
+
+ @Override
+ public String getFingerprint() {
+ return "changed";
+ }
+ }, insertionDelay);
+ }
+
+ @Test
+ public void soneWithoutOriginalFingerprintIsEligibleAfter60Seconds() {
+ SoneModificationDetector soneModificationDetector = createDetectorWithoutOriginalFingerprint();
+ assertThat(soneModificationDetector.isEligibleForInsert(), is(false));
+ passTime(60);
+ assertThat(soneModificationDetector.isEligibleForInsert(), is(true));
+ }
+
}
+++ /dev/null
-package net.pterodactylus.sone.utils;
-
-import java.util.Arrays;
-import java.util.List;
-
-import com.google.common.base.Optional;
-import com.google.common.collect.FluentIterable;
-import org.hamcrest.MatcherAssert;
-import org.hamcrest.Matchers;
-import org.junit.Test;
-
-/**
- * Unit test for {@link Optionals}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class OptionalsTest {
-
- private final Object object1 = new Object();
- private final Object object2 = new Object();
- private final Object object3 = new Object();
-
- @Test
- public void canCreateOptionals() {
- new Optionals();
- }
-
- @Test
- public void isPresentFiltersCorrectOptionals() {
- List<Optional<Object>> optionals = Arrays.asList(
- Optional.of(object1), Optional.absent(),
- Optional.of(object2), Optional.absent(),
- Optional.of(object3), Optional.absent()
- );
- List<Optional<Object>> filteredOptionals =
- FluentIterable.from(optionals).filter(Optionals.isPresent()).toList();
- MatcherAssert.assertThat(filteredOptionals, Matchers.contains(
- Optional.of(object1), Optional.of(object2), Optional.of(object3)));
- }
-
- @Test
- public void getReturnsCorrectValues() {
- List<Optional<Object>> optionals = Arrays.asList(
- Optional.of(object1),
- Optional.of(object2),
- Optional.of(object3)
- );
- List<Object> objects = FluentIterable.from(optionals).transform(Optionals.get()).toList();
- MatcherAssert.assertThat(objects, Matchers.contains(object1, object2, object3));
- }
-
-}