package net.pterodactylus.sone.core;
+import static freenet.keys.USK.create;
+import static java.lang.String.format;
+import static java.util.logging.Level.WARNING;
+import static net.pterodactylus.sone.freenet.Key.routingKey;
+
import java.net.MalformedURLException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
-import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
}
}
- public void registerUsk(final Sone sone, final SoneUpdater soneUpdater) {
+ public void registerActiveUsk(FreenetURI requestUri,
+ USKCallback uskCallback) {
try {
- logger.log(Level.FINE, String.format("Registering Sone “%s” for USK updates at %s…", sone, sone.getRequestUri().setMetaString(new String[] { "sone.xml" })));
- USKCallback uskCallback = new USKCallback() {
-
- @Override
- @SuppressWarnings("synthetic-access")
- public void onFoundEdition(long edition, USK key, ObjectContainer objectContainer, ClientContext clientContext, boolean metadata, short codec, byte[] data, boolean newKnownGood, boolean newSlotToo) {
- logger.log(Level.FINE, String.format("Found USK update for Sone “%s” at %s, new known good: %s, new slot too: %s.", sone, key, newKnownGood, newSlotToo));
- soneUpdater.updateSone(edition);
- }
-
- @Override
- public short getPollingPriorityProgress() {
- return RequestStarter.INTERACTIVE_PRIORITY_CLASS;
- }
+ soneUskCallbacks.put(routingKey(requestUri), uskCallback);
+ node.clientCore.uskManager.subscribe(create(requestUri),
+ uskCallback, true, (RequestClient) client);
+ } catch (MalformedURLException mue1) {
+ logger.log(WARNING, format("Could not subscribe USK “%s”!",
+ requestUri), mue1);
+ }
+ }
- @Override
- public short getPollingPriorityNormal() {
- return RequestStarter.INTERACTIVE_PRIORITY_CLASS;
- }
- };
- soneUskCallbacks.put(sone.getId(), uskCallback);
- boolean runBackgroundFetch = (System.currentTimeMillis() - sone.getTime()) < TimeUnit.DAYS.toMillis(7);
- node.clientCore.uskManager.subscribe(USK.create(sone.getRequestUri()), uskCallback, runBackgroundFetch, (RequestClient) client);
+ public void registerPassiveUsk(FreenetURI requestUri,
+ USKCallback uskCallback) {
+ try {
+ soneUskCallbacks.put(routingKey(requestUri), uskCallback);
+ node.clientCore
+ .uskManager
+ .subscribe(create(requestUri), uskCallback, false,
+ (RequestClient) client);
} catch (MalformedURLException mue1) {
- logger.log(Level.WARNING, String.format("Could not subscribe USK “%s”!", sone.getRequestUri()), mue1);
+ logger.log(WARNING,
+ format("Could not subscribe USK “%s”!", requestUri),
+ mue1);
}
}
package net.pterodactylus.sone.core;
+import static java.lang.String.format;
+import static java.lang.System.currentTimeMillis;
+import static java.util.concurrent.TimeUnit.DAYS;
+
import java.io.InputStream;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.pterodactylus.util.xml.XML;
import freenet.client.FetchResult;
+import freenet.client.async.ClientContext;
+import freenet.client.async.USKCallback;
import freenet.keys.FreenetURI;
+import freenet.keys.USK;
+import freenet.node.RequestStarter;
import freenet.support.api.Bucket;
+import com.db4o.ObjectContainer;
+
import com.google.common.annotations.VisibleForTesting;
import org.w3c.dom.Document;
if (!sones.add(sone)) {
freenetInterface.unregisterUsk(sone);
}
- freenetInterface.registerUsk(sone, new SoneUpdater() {
+ final USKCallback uskCallback = new USKCallback() {
+
@Override
- public void updateSone(long edition) {
+ @SuppressWarnings("synthetic-access")
+ public void onFoundEdition(long edition, USK key,
+ ObjectContainer objectContainer,
+ ClientContext clientContext, boolean metadata,
+ short codec, byte[] data, boolean newKnownGood,
+ boolean newSlotToo) {
+ logger.log(Level.FINE, format(
+ "Found USK update for Sone “%s” at %s, new known good: %s, new slot too: %s.",
+ sone, key, newKnownGood, newSlotToo));
if (edition > sone.getLatestEdition()) {
sone.setLatestEdition(edition);
new Thread(fetchSoneAction(sone),
"Sone Downloader").start();
}
}
- });
+
+ @Override
+ public short getPollingPriorityProgress() {
+ return RequestStarter.INTERACTIVE_PRIORITY_CLASS;
+ }
+
+ @Override
+ public short getPollingPriorityNormal() {
+ return RequestStarter.INTERACTIVE_PRIORITY_CLASS;
+ }
+ };
+ if (soneHasBeenActiveRecently(sone)) {
+ freenetInterface.registerActiveUsk(sone.getRequestUri(),
+ uskCallback);
+ } else {
+ freenetInterface.registerPassiveUsk(sone.getRequestUri(),
+ uskCallback);
+ }
+ }
+
+ private boolean soneHasBeenActiveRecently(Sone sone) {
+ return (currentTimeMillis() - sone.getTime()) < DAYS.toMillis(7);
}
private void fetchSone(Sone sone) {
+++ /dev/null
-package net.pterodactylus.sone.core;
-
-import net.pterodactylus.sone.data.Sone;
-
-/**
- * Component that decides whether a Sone needs to be downloaded because a
- * newer edition was found.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface SoneUpdater {
-
- void updateSone(long edition);
-
-}
import java.util.List;
import java.util.Set;
-import net.pterodactylus.sone.core.Options;
+import javax.annotation.Nonnegative;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
import net.pterodactylus.sone.freenet.wot.Identity;
import net.pterodactylus.sone.freenet.wot.OwnIdentity;
import net.pterodactylus.sone.template.SoneAccessor;
import freenet.keys.FreenetURI;
+import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.primitives.Ints;
}
};
+ 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();
+ }
+ };
+
/**
* Returns the identity of this Sone.
*
--- /dev/null
+package net.pterodactylus.sone.freenet;
+
+import static freenet.support.Base64.encode;
+import static java.lang.String.format;
+
+import freenet.keys.FreenetURI;
+
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * Encapsulates the parts of a {@link FreenetURI} that do not change while
+ * being converted from SSK to USK and/or back.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class Key {
+
+ private final byte[] routingKey;
+ private final byte[] cryptoKey;
+ private final byte[] extra;
+
+ private Key(byte[] routingKey, byte[] cryptoKey, byte[] extra) {
+ this.routingKey = routingKey;
+ this.cryptoKey = cryptoKey;
+ this.extra = extra;
+ }
+
+ @VisibleForTesting
+ public String getRoutingKey() {
+ return encode(routingKey);
+ }
+
+ @VisibleForTesting
+ public String getCryptoKey() {
+ return encode(cryptoKey);
+ }
+
+ @VisibleForTesting
+ public String getExtra() {
+ return encode(extra);
+ }
+
+ public FreenetURI toUsk(String docName, long edition, String... paths) {
+ return new FreenetURI("USK", docName, paths, routingKey, cryptoKey,
+ extra, edition);
+ }
+
+ public FreenetURI toSsk(String docName, String... paths) {
+ return new FreenetURI("SSK", docName, paths, routingKey, cryptoKey,
+ extra);
+ }
+
+ public FreenetURI toSsk(String docName, long edition, String... paths) {
+ return new FreenetURI("SSK", format("%s-%d", docName, edition), paths,
+ routingKey, cryptoKey, extra, edition);
+ }
+
+ public static Key from(FreenetURI freenetURI) {
+ return new Key(freenetURI.getRoutingKey(), freenetURI.getCryptoKey(),
+ freenetURI.getExtra());
+ }
+
+ public static String routingKey(FreenetURI freenetURI) {
+ return from(freenetURI).getRoutingKey();
+ }
+
+}
import static freenet.keys.InsertableClientSSK.createRandom;
import static freenet.node.RequestStarter.INTERACTIVE_PRIORITY_CLASS;
import static freenet.node.RequestStarter.PREFETCH_PRIORITY_CLASS;
-import static java.lang.System.currentTimeMillis;
-import static java.util.concurrent.TimeUnit.DAYS;
-import static java.util.concurrent.TimeUnit.SECONDS;
import static net.pterodactylus.sone.Matchers.delivers;
import static net.pterodactylus.sone.TestUtil.setFinalField;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyShort;
import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.HashMap;
-import java.util.concurrent.CountDownLatch;
import net.pterodactylus.sone.TestUtil;
import net.pterodactylus.sone.core.FreenetInterface.Callback;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
/**
* Unit test for {@link FreenetInterface}.
private FreenetInterface freenetInterface;
private final Sone sone = mock(Sone.class);
private final ArgumentCaptor<USKCallback> callbackCaptor = forClass(USKCallback.class);
- private final SoneDownloader soneDownloader = mock(SoneDownloader.class);
- private final SoneUpdater soneUpdater = mock(SoneUpdater.class);
private final Image image = mock(Image.class);
private InsertToken insertToken;
@Test
public void soneWithWrongRequestUriWillNotBeSubscribed() throws MalformedURLException {
when(sone.getRequestUri()).thenReturn(new FreenetURI("KSK@GPLv3.txt"));
- freenetInterface.registerUsk(sone, null);
+ freenetInterface.registerUsk(new FreenetURI("KSK@GPLv3.txt"), null);
verify(uskManager, never()).subscribe(any(USK.class), any(USKCallback.class), anyBoolean(), any(RequestClient.class));
}
@Test
- public void registeringAUskForARecentlyModifiedSone() throws MalformedURLException {
- when(sone.getTime()).thenReturn(currentTimeMillis() - DAYS.toMillis(1));
- freenetInterface.registerUsk(sone, null);
- verify(uskManager).subscribe(any(USK.class), any(USKCallback.class), eq(true), eq((RequestClient) highLevelSimpleClient));
- }
-
- @Test
- public void registeringAUskForAnOldSone() throws MalformedURLException {
- when(sone.getTime()).thenReturn(currentTimeMillis() - DAYS.toMillis(365));
- freenetInterface.registerUsk(sone, null);
- verify(uskManager).subscribe(any(USK.class), any(USKCallback.class), eq(false), eq((RequestClient) highLevelSimpleClient));
- }
-
- @Test
public void registeringAUsk() {
FreenetURI freenetUri = createRandom(randomSource, "test-0").getURI().uskForSSK();
Callback callback = mock(Callback.class);
}
@Test
- public void unregisteringARegisteredSoneUnregistersTheSone() {
- freenetInterface.registerUsk(sone, null);
+ public void unregisteringARegisteredSoneUnregistersTheSone()
+ throws MalformedURLException {
+ freenetInterface.registerActiveUsk(sone.getRequestUri(), mock(USKCallback.class));
freenetInterface.unregisterUsk(sone);
verify(uskManager).unsubscribe(any(USK.class), any(USKCallback.class));
}
@Test
public void unregisteringASoneWithAWrongRequestKeyWillNotUnsubscribe() throws MalformedURLException {
- freenetInterface.registerUsk(sone, null);
when(sone.getRequestUri()).thenReturn(new FreenetURI("KSK@GPLv3.txt"));
+ freenetInterface.registerUsk(sone.getRequestUri(), null);
freenetInterface.unregisterUsk(sone);
verify(uskManager, never()).unsubscribe(any(USK.class), any(USKCallback.class));
}
@Test
- public void callbackPrioritiesAreInteractive() {
- freenetInterface.registerUsk(sone, null);
- assertThat(callbackCaptor.getValue().getPollingPriorityNormal(), is(INTERACTIVE_PRIORITY_CLASS));
- assertThat(callbackCaptor.getValue().getPollingPriorityProgress(), is(INTERACTIVE_PRIORITY_CLASS));
- }
-
- @Test
- public void callbackForRegisteredSoneWithHigherEditionTriggersDownload() throws InterruptedException {
- freenetInterface.registerUsk(sone, soneUpdater);
- callbackCaptor.getValue().onFoundEdition(1, null, null, null, false, (short) 0, null, false, false);
- verify(soneUpdater).updateSone(1);
- }
-
- @Test
public void callbackForNormalUskUsesDifferentPriorities() {
Callback callback = mock(Callback.class);
- FreenetURI uri = createRandom(randomSource, "test-0").getURI().uskForSSK();
- freenetInterface.registerUsk(uri, callback);
+ FreenetURI soneUri = createRandom(randomSource, "test-0").getURI().uskForSSK();
+ freenetInterface.registerUsk(soneUri, callback);
assertThat(callbackCaptor.getValue().getPollingPriorityNormal(), is(PREFETCH_PRIORITY_CLASS));
assertThat(callbackCaptor.getValue().getPollingPriorityProgress(), is(INTERACTIVE_PRIORITY_CLASS));
}
package net.pterodactylus.sone.core;
import static com.google.common.base.Optional.of;
+import static java.lang.System.currentTimeMillis;
import static java.util.UUID.randomUUID;
+import static java.util.concurrent.TimeUnit.DAYS;
import static net.pterodactylus.sone.data.Sone.SoneStatus.downloading;
import static net.pterodactylus.sone.data.Sone.SoneStatus.idle;
import static net.pterodactylus.sone.data.Sone.SoneStatus.unknown;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import net.pterodactylus.sone.core.FreenetInterface.Fetched;
import net.pterodactylus.sone.data.Album;
import freenet.client.ClientMetadata;
import freenet.client.FetchResult;
+import freenet.client.async.ClientContext;
+import freenet.client.async.USKCallback;
import freenet.keys.FreenetURI;
+import freenet.keys.USK;
+import freenet.node.RequestStarter;
import freenet.support.api.Bucket;
+import com.db4o.ObjectContainer;
+
import com.google.common.base.Optional;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableSet;
private final Core core = mock(Core.class);
private final FreenetInterface freenetInterface = mock(FreenetInterface.class);
private final SoneDownloaderImpl soneDownloader = new SoneDownloaderImpl(core, freenetInterface);
- private final SoneUpdater soneUpdater = mock(SoneUpdater.class);
private final FreenetURI requestUri = mock(FreenetURI.class);
private final Sone sone = mock(Sone.class);
private final PostBuilder postBuilder = mock(PostBuilder.class);
when(sone.getId()).thenReturn("identity");
when(sone.getIdentity()).thenReturn(identity);
when(sone.getRequestUri()).thenReturn(requestUri);
+ when(sone.getTime()).thenReturn(currentTimeMillis() - DAYS.toMillis(1));
+ }
+
+ private void setupSoneAsUnknown() {
+ when(sone.getTime()).thenReturn(0L);
}
@Before
@Test
public void addingASoneWillRegisterItsKey() {
soneDownloader.addSone(sone);
- verify(freenetInterface).registerUsk(eq(sone), any(SoneUpdater.class));
+ verify(freenetInterface).registerActiveUsk(eq(sone.getRequestUri()), any(
+ USKCallback.class));
verify(freenetInterface, never()).unregisterUsk(sone);
}
public void addingASoneTwiceWillAlsoDeregisterItsKey() {
soneDownloader.addSone(sone);
soneDownloader.addSone(sone);
- verify(freenetInterface, times(2)).registerUsk(eq(sone), any(SoneUpdater.class));
+ verify(freenetInterface, times(2)).registerActiveUsk(eq(
+ sone.getRequestUri()), any(USKCallback.class));
verify(freenetInterface).unregisterUsk(sone);
}
@Test
public void notBeingAbleToFetchAnUnknownSoneDoesNotUpdateCore() {
+ setupSoneAsUnknown();
soneDownloader.fetchSoneAction(sone).run();
verify(freenetInterface).fetchUri(requestUri);
verifyThatSoneStatusWasChangedToDownloadingAndBackTo(unknown);
@Test
public void notBeingAbleToFetchAKnownSoneDoesNotUpdateCore() {
- when(sone.getTime()).thenReturn(1000L);
soneDownloader.fetchSoneAction(sone).run();
verify(freenetInterface).fetchUri(requestUri);
verifyThatSoneStatusWasChangedToDownloadingAndBackTo(idle);
@Test(expected = NullPointerException.class)
public void exceptionWhileFetchingAnUnknownSoneDoesNotUpdateCore() {
+ setupSoneAsUnknown();
when(freenetInterface.fetchUri(requestUri)).thenThrow(NullPointerException.class);
try {
soneDownloader.fetchSoneAction(sone).run();
@Test(expected = NullPointerException.class)
public void exceptionWhileFetchingAKnownSoneDoesNotUpdateCore() {
- when(sone.getTime()).thenReturn(1000L);
when(freenetInterface.fetchUri(requestUri)).thenThrow(NullPointerException.class);
try {
soneDownloader.fetchSoneAction(sone).run();
when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
soneDownloader.fetchSone(sone, sone.getRequestUri(), true);
verify(core, never()).updateSone(any(Sone.class));
- verifyThatSoneStatusWasChangedToDownloadingAndBackTo(unknown);
+ verifyThatSoneStatusWasChangedToDownloadingAndBackTo(idle);
}
private Fetched createFetchResult(FreenetURI uri, InputStream inputStream) throws IOException {
--- /dev/null
+package net.pterodactylus.sone.freenet;
+
+import static freenet.support.Base64.encode;
+import static net.pterodactylus.sone.freenet.Key.from;
+import static net.pterodactylus.sone.freenet.Key.routingKey;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+import java.net.MalformedURLException;
+
+import freenet.keys.FreenetURI;
+
+import org.junit.Test;
+
+/**
+ * Unit test for {@link Key}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class KeyTest {
+
+ private final FreenetURI uri;
+ private final Key key;
+
+ public KeyTest() throws MalformedURLException {
+ uri = new FreenetURI(
+ "SSK@NfUYvxDwU9vqb2mh-qdT~DYJ6U0XNbxMGGoLe0aCHJs,Miglsgix0VR56ZiPl4NgjnUd~UdrnHqIvXJ3KKHmxmI,AQACAAE/some-site-12/foo/bar.html");
+ key = from(uri);
+ }
+
+ @Test
+ public void keyCanBeCreatedFromFreenetUri() throws MalformedURLException {
+ assertThat(key.getRoutingKey(),
+ is("NfUYvxDwU9vqb2mh-qdT~DYJ6U0XNbxMGGoLe0aCHJs"));
+ assertThat(key.getCryptoKey(),
+ is("Miglsgix0VR56ZiPl4NgjnUd~UdrnHqIvXJ3KKHmxmI"));
+ assertThat(key.getExtra(), is("AQACAAE"));
+ }
+
+ @Test
+ public void keyCanBeConvertedToUsk() throws MalformedURLException {
+ FreenetURI uskUri = key.toUsk("other-site", 15, "some", "path.html");
+ assertThat(uskUri.toString(),
+ is("USK@NfUYvxDwU9vqb2mh-qdT~DYJ6U0XNbxMGGoLe0aCHJs,Miglsgix0VR56ZiPl4NgjnUd~UdrnHqIvXJ3KKHmxmI,AQACAAE/other-site/15/some/path.html"));
+ }
+
+ @Test
+ public void keyCanBeConvertedToSskWithoutEdition()
+ throws MalformedURLException {
+ FreenetURI uskUri = key.toSsk("other-site", "some", "path.html");
+ assertThat(uskUri.toString(),
+ is("SSK@NfUYvxDwU9vqb2mh-qdT~DYJ6U0XNbxMGGoLe0aCHJs,Miglsgix0VR56ZiPl4NgjnUd~UdrnHqIvXJ3KKHmxmI,AQACAAE/other-site/some/path.html"));
+ }
+
+ @Test
+ public void keyCanBeConvertedToSskWithEdition()
+ throws MalformedURLException {
+ FreenetURI uskUri = key.toSsk("other-site", 15, "some", "path.html");
+ assertThat(uskUri.toString(),
+ is("SSK@NfUYvxDwU9vqb2mh-qdT~DYJ6U0XNbxMGGoLe0aCHJs,Miglsgix0VR56ZiPl4NgjnUd~UdrnHqIvXJ3KKHmxmI,AQACAAE/other-site-15/some/path.html"));
+ }
+
+ @Test
+ public void routingKeyIsExtractCorrectly() {
+ assertThat(routingKey(uri),
+ is("NfUYvxDwU9vqb2mh-qdT~DYJ6U0XNbxMGGoLe0aCHJs"));
+ }
+
+}