}
/**
+ * Sets the edition to rescue.
+ *
+ * @param edition
+ * The edition to rescue
+ * @return This Sone rescuer
+ */
+ public SoneRescuer setEdition(long edition) {
+ currentEdition = edition;
+ return this;
+ }
+
+ /**
* Sets whether the last fetch was successful.
*
* @return {@code true} if the last fetch was successful, {@code false}
//
/**
- * Starts the next fetch.
+ * Starts the next fetch. If you want to fetch a different edition than “the
+ * next older one,” remember to call {@link #setEdition(long)} before
+ * calling this method.
*/
public void startNextFetch() {
fetching = true;
notifySyncObject();
}
- /**
- * Starts the next fetch.
- */
- public void startNextFetchWithSkip() {
- currentEdition--;
- fetching = true;
- notifySyncObject();
- }
-
//
// SERVICE METHODS
//
}
if (fetching) {
core.lockSone(sone);
- FreenetURI soneUri = sone.getRequestUri().setKeyType("SSK").setDocName("Sone-" + getNextEdition()).setMetaString(new String[] { "sone.xml" });
+ FreenetURI soneUri = sone.getRequestUri().setKeyType("SSK").setDocName("Sone-" + currentEdition).setMetaString(new String[] { "sone.xml" });
System.out.println("URI: " + soneUri);
Sone fetchedSone = soneDownloader.fetchSone(sone, soneUri, true);
System.out.println("Sone: " + fetchedSone);
lastFetchSuccessful = (fetchedSone != null);
if (lastFetchSuccessful) {
core.updateSone(fetchedSone, true);
- currentEdition = getNextEdition();
}
fetching = false;
}
val soneRescuer = soneRequest.core.getSoneRescuer(currentSone)
templateContext["soneRescuer"] = soneRescuer
if (soneRequest.isPOST) {
+ soneRequest.parameters["edition", 9]?.toIntOrNull()?.also {
+ if (it > -1) {
+ soneRescuer.setEdition(it.toLong())
+ }
+ }
if (soneRequest.parameters["fetch", 8] == "true") {
soneRescuer.startNextFetch()
- } else if (soneRequest.parameters["fetchSkip"] == "true") {
- soneRescuer.startNextFetchWithSkip()
}
- redirectTo("rescue.html")
+ throw RedirectException("rescue.html")
}
}
Page.Rescue.Text.NotFetched=Die Sonerettung konnte Version {0} Ihrer Sone nicht herunter laden. Bitte versuchen Sie erneut, Version {0} herunter zu laden, oder versuchen Sie die nächstältere Version.
Page.Rescue.Label.NextEdition=Nächste Version
Page.Rescue.Button.Fetch=Version herunter laden
-Page.Rescue.Button.FetchSkip=Eine Version überspringen
Page.NoPermission.Title=Unberechtigter Zugriff - Sone
Page.NoPermission.Page.Title=Unberechtigter Zugriff
Page.Rescue.Text.NotFetched=The Sone Rescuer could not download edition {0} of your Sone. Please either try again with edition {0}, or try the next older edition.
Page.Rescue.Label.NextEdition=Next edition
Page.Rescue.Button.Fetch=Fetch edition
-Page.Rescue.Button.FetchSkip=Skip one edition
Page.NoPermission.Title=Unauthorized Access - Sone
Page.NoPermission.Page.Title=Unauthorized Access
Page.Rescue.Text.NotFetched=El rescatador de Sone no ha podido descargar la edición {0} de tu Sone. Por favor, vuelve ha intentarlo con la edición {0}, o prueba con la siguiente versión antigua.
Page.Rescue.Label.NextEdition=Siguiente edición
Page.Rescue.Button.Fetch=Obtener edición
-Page.Rescue.Button.FetchSkip=Skip one edition
Page.NoPermission.Title=Acceso desautorizado - Sone
Page.NoPermission.Page.Title=Acceso desautorizado
Notification.SoneIsInserting.Text=Tu Sone sone://{0} está siendo insertado.
Notification.SoneIsInserted.Text=Tu Sone sone://{0} ha sido insertado en {1,number} {1,choice,0#segundos|1#segundo|1<segundos}.
Notification.SoneInsertAborted.Text=Tu Sone sone://{0} no pudo ser insertado.
-# 55-61, 310
+# 55-61
Page.Rescue.Text.NotFetched=Le récupérateur de Sone ne peut pas restaurer la version {0} de votre Sone. Merci de réessayer, ou essayez avec une version plus ancienne.
Page.Rescue.Label.NextEdition=Prochaine version
Page.Rescue.Button.Fetch=Récupérer la version.
-Page.Rescue.Button.FetchSkip=Skip one edition
Page.NoPermission.Title=Accès non autorisé - Sone
Page.NoPermission.Page.Title=Accès non autorisé
Notification.SoneIsInserting.Text=Votre Sone sone://{0} va maintenant être inséré.
Notification.SoneIsInserted.Text=votre Sone sone://{0} a été inséré dans {1,number} {1,choice,0#seconds|1#second|1<seconds}.
Notification.SoneInsertAborted.Text=Votre Sone sone://{0} ne peut pas être inséré.
-# 55-61, 310
+# 55-61
Page.Rescue.Text.NotFetched=Sone復帰モードは{0}の版を読み込むことはできませんでした。{0}の版を試してみるか、さらに次の版を試してみてください。
Page.Rescue.Label.NextEdition=次の版
Page.Rescue.Button.Fetch=版を取り出す
-Page.Rescue.Button.FetchSkip=Skip one edition
Page.NoPermission.Title=不正なアクセス - Sone
Page.NoPermission.Page.Title=不正なアクセス
Notification.SoneIsInserting.Text=あなたのSone sone://{0}は現在インサート中です。
Notification.SoneIsInserted.Text=あなたのSone sone://{0}は{1,number}{1,choice,0#秒|1#秒|1<秒}でインサートされました。
Notification.SoneInsertAborted.Text=あなたのSone sone://{0}のインサートに失敗しました。
-# 55-51, 67, 107, 310, 466
+# 55-51, 67, 107, 465
Page.Rescue.Text.NotFetched=Sone-redderen kunne ikke laste ned utgave {0} av din Sone. Enten prøv igjen på nytt med utgave {0}, eller prøv igjen med utgaven før.
Page.Rescue.Label.NextEdition=Neste utgave
Page.Rescue.Button.Fetch=Hent utgave
-Page.Rescue.Button.FetchSkip=Skip one edition
Page.NoPermission.Title=Ikke-autorisert tilgang - Sone
Page.NoPermission.Page.Title=Ikke-autorisert tilgang
Notification.SoneIsInserting.Text=Your Sone sone://{0} is now being inserted.
Notification.SoneIsInserted.Text=Your Sone sone://{0} has been inserted in {1,number} {1,choice,0#seconds|1#second|1<seconds}.
Notification.SoneInsertAborted.Text=Your Sone sone://{0} could not be inserted.
-# 55-61, 67, 107, 127-128, 310, 316-318, 320-322, 466, 472-474
+# 55-61, 67, 107, 127-128, 315-317, 319-321, 465, 471-473
Page.Rescue.Text.NotFetched=Tryb Ratunkowy Sone nie mógł sćiągnąć edycji {0} twojego Sone. Spróbuj pobrać ponownie edycję {0}, albo pobierz kolejną starszą edycję.
Page.Rescue.Label.NextEdition=Następna edycja
Page.Rescue.Button.Fetch=Pobierz edycję
-Page.Rescue.Button.FetchSkip=Skip one edition
Page.NoPermission.Title=Nieupoważniony dostęp- Sone
Page.NoPermission.Page.Title=Nieupoważniony dostęp
Notification.SoneIsInserting.Text=Twoje Sone sone://{0} jest w tej chili wysyłane.
Notification.SoneIsInserted.Text=Twoje sone://{0} zostało wysłane w {1,number} {1,choice,0#seconds|1#second|1<seconds}.
Notification.SoneInsertAborted.Text=Twoje Sone sone://{0} nie mogło zostać wysłane.
-# 55-61, 310, 466
+# 55-61, 465
Page.Rescue.Text.NotFetched=Восстановитель Sone не смог скачать редакцию {0} вашего Sone. Пожалуйста, либо попытайтесь снова с редакцией {0}, либо попробуйте более старую редакцию.
Page.Rescue.Label.NextEdition=Следующая редакция
Page.Rescue.Button.Fetch=Загрузить редакцию
-Page.Rescue.Button.FetchSkip=Skip one edition
Page.NoPermission.Title=Неавторизованный доступ - Sone
Page.NoPermission.Page.Title=Неавторизованный доступ
Notification.SoneIsInserting.Text=Your Sone sone://{0} is now being inserted.
Notification.SoneIsInserted.Text=Your Sone sone://{0} has been inserted in {1,number} {1,choice,0#seconds|1#second|1<seconds}.
Notification.SoneInsertAborted.Text=Your Sone sone://{0} could not be inserted.
-# 55-61, 67, 107, 127-128, 310, 316-318, 320-322, 466, 472-474
+# 55-61, 67, 107, 127-128, 315-317, 319-321, 465, 471-473
<%if soneRescuer.lastFetchSuccessful>
<p><%= Page.Rescue.Text.Fetched|l10n 0=soneRescuer.currentEdition|html></p>
<%else>
- <p><%= Page.Rescue.Text.NotFetched|l10n 0=soneRescuer.nextEdition|html></p>
+ <p><%= Page.Rescue.Text.NotFetched|l10n 0=soneRescuer.currentEdition|html></p>
<%/if>
<form action="rescue.html" method="post">
<input type="hidden" name="formPassword" value="<%formPassword|html>" />
- <div>
- <%= Page.Rescue.Label.NextEdition|l10n|html>: <%soneRescuer.nextEdition>
- <button type="submit" name="fetch" value="true"><%= Page.Rescue.Button.Fetch|l10n|html></button>
- <button type="submit" name="fetchSkip" value="true"><%= Page.Rescue.Button.FetchSkip|l10n|html></button>
- </div>
+ <label><%= Page.Rescue.Label.NextEdition|l10n|html></label>
+ <input type="field" name="edition" value="<%soneRescuer.nextEdition>" />
+ <button type="submit" name="fetch" value="true"><%= Page.Rescue.Button.Fetch|l10n|html></button>
</form>
<%else>
<%if soneRescuer.lastFetchSuccessful>
--- /dev/null
+package net.pterodactylus.sone.core;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import net.pterodactylus.sone.data.Sone;
+
+import freenet.keys.FreenetURI;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/**
+ * Unit test for {@link SoneRescuer}.
+ */
+public class SoneRescuerTest {
+
+ private static final long CURRENT_EDITION = 12L;
+ private static final long SOME_OTHER_EDITION = 15L;
+ private final Core core = mock(Core.class);
+ private final SoneDownloader soneDownloader = mock(SoneDownloader.class);
+ private final Sone sone = mock(Sone.class);
+ private SoneRescuer soneRescuer;
+
+ @Before
+ public void setupSone() {
+ FreenetURI soneUri = mock(FreenetURI.class);
+ when(soneUri.getEdition()).thenReturn(CURRENT_EDITION);
+ when(sone.getRequestUri()).thenReturn(soneUri);
+ }
+
+ @Before
+ public void setupSoneRescuer() {
+ soneRescuer = new SoneRescuer(core, soneDownloader, sone);
+ }
+
+ @Test
+ public void newSoneRescuerIsNotFetchingAnything() {
+ assertThat(soneRescuer.isFetching(), is(false));
+ }
+
+ @Test
+ public void newSoneRescuerStartsAtCurrentEditionOfSone() {
+ assertThat(soneRescuer.getCurrentEdition(), is(CURRENT_EDITION));
+ }
+
+ @Test
+ public void newSoneRescuerHasANextEditionToGet() {
+ assertThat(soneRescuer.hasNextEdition(), is(true));
+ }
+
+ @Test
+ public void soneRescuerDoesNotHaveANextEditionIfCurrentEditionIsZero() {
+ when(sone.getRequestUri().getEdition()).thenReturn(0L);
+ soneRescuer = new SoneRescuer(core, soneDownloader, sone);
+ assertThat(soneRescuer.hasNextEdition(), is(false));
+ }
+
+ @Test
+ public void nextEditionIsOneSmallerThanTheCurrentEdition() {
+ assertThat(soneRescuer.getNextEdition(), is(CURRENT_EDITION - 1));
+ }
+
+ @Test
+ public void currentEditionCanBeSet() {
+ soneRescuer.setEdition(SOME_OTHER_EDITION);
+ assertThat(soneRescuer.getCurrentEdition(), is(SOME_OTHER_EDITION));
+ }
+
+ @Test
+ public void lastFetchOfANewSoneRescuerWasSuccessful() {
+ assertThat(soneRescuer.isLastFetchSuccessful(), is(true));
+ }
+
+ @Test
+ public void mainLoopStopsWhenItShould() {
+ soneRescuer.stop();
+ soneRescuer.serviceRun();
+ }
+
+ @Test
+ public void successfulInsert() {
+ final Sone fetchedSone = mock(Sone.class);
+ returnUriOnInsert(fetchedSone);
+ soneRescuer.startNextFetch();
+ soneRescuer.serviceRun();
+ verify(core).lockSone(eq(sone));
+ verify(core).updateSone(eq(fetchedSone), eq(true));
+ assertThat(soneRescuer.isLastFetchSuccessful(), is(true));
+ assertThat(soneRescuer.isFetching(), is(false));
+ }
+
+ @Test
+ public void nonSuccessfulInsertIsRecognized() {
+ returnUriOnInsert(null);
+ soneRescuer.startNextFetch();
+ soneRescuer.serviceRun();
+ verify(core).lockSone(eq(sone));
+ verify(core, never()).updateSone(any(Sone.class), eq(true));
+ assertThat(soneRescuer.isLastFetchSuccessful(), is(false));
+ assertThat(soneRescuer.isFetching(), is(false));
+ }
+
+ private void returnUriOnInsert(final Sone fetchedSone) {
+ FreenetURI keyWithMetaStrings = setupFreenetUri();
+ doAnswer(new Answer<Sone>() {
+ @Override
+ public Sone answer(InvocationOnMock invocation) throws Throwable {
+ soneRescuer.stop();
+ return fetchedSone;
+ }
+ }).when(soneDownloader).fetchSone(eq(sone), eq(keyWithMetaStrings), eq(true));
+ }
+
+ private FreenetURI setupFreenetUri() {
+ FreenetURI sskKey = mock(FreenetURI.class);
+ FreenetURI keyWithDocName = mock(FreenetURI.class);
+ FreenetURI keyWithMetaStrings = mock(FreenetURI.class);
+ when(keyWithDocName.setMetaString(eq(new String[] { "sone.xml" }))).thenReturn(keyWithMetaStrings);
+ when(sskKey.setDocName(eq("Sone-" + CURRENT_EDITION))).thenReturn(keyWithDocName);
+ when(sone.getRequestUri().setKeyType(eq("SSK"))).thenReturn(sskKey);
+ return keyWithMetaStrings;
+ }
+
+}
+++ /dev/null
-package net.pterodactylus.sone.core
-
-import freenet.keys.*
-import net.pterodactylus.sone.data.*
-import net.pterodactylus.sone.test.*
-import org.hamcrest.MatcherAssert.*
-import org.hamcrest.Matchers.equalTo
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mockito.doAnswer
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import kotlin.test.Test
-
-/**
- * Unit test for [SoneRescuer].
- */
-class SoneRescuerTest {
-
- private val core = mock<Core>()
- private val soneDownloader = mock<SoneDownloader>()
- private val sone = mock<Sone>().apply {
- val soneUri = mock<FreenetURI>()
- whenever(soneUri.edition).thenReturn(currentEdition)
- whenever(requestUri).thenReturn(soneUri)
- }
- private val soneRescuer = SoneRescuer(core, soneDownloader, sone)
-
- @Test
- fun newSoneRescuerIsNotFetchingAnything() {
- assertThat(soneRescuer.isFetching, equalTo(false))
- }
-
- @Test
- fun newSoneRescuerStartsAtCurrentEditionOfSone() {
- assertThat(soneRescuer.currentEdition, equalTo(currentEdition))
- }
-
- @Test
- fun newSoneRescuerHasANextEditionToGet() {
- assertThat(soneRescuer.hasNextEdition(), equalTo(true))
- }
-
- @Test
- fun soneRescuerDoesNotHaveANextEditionIfCurrentEditionIsZero() {
- whenever(sone.requestUri.edition).thenReturn(0L)
- val soneRescuer = SoneRescuer(core, soneDownloader, sone)
- assertThat(soneRescuer.hasNextEdition(), equalTo(false))
- }
-
- @Test
- fun nextEditionIsOneSmallerThanTheCurrentEdition() {
- assertThat(soneRescuer.nextEdition, equalTo(currentEdition - 1))
- }
-
- @Test
- fun lastFetchOfANewSoneRescuerWasSuccessful() {
- assertThat(soneRescuer.isLastFetchSuccessful, equalTo(true))
- }
-
- @Test
- fun mainLoopStopsWhenItShould() {
- soneRescuer.stop()
- soneRescuer.serviceRun()
- }
-
- @Test
- fun successfulInsert() {
- val fetchedSone = mock<Sone>()
- returnUriOnInsert(fetchedSone, currentEdition - 1)
- soneRescuer.startNextFetch()
- soneRescuer.serviceRun()
- verify(core).lockSone(eq(sone))
- verify(core).updateSone(eq(fetchedSone), eq(true))
- assertThat(soneRescuer.isLastFetchSuccessful, equalTo(true))
- assertThat(soneRescuer.isFetching, equalTo(false))
- assertThat(soneRescuer.currentEdition, equalTo(currentEdition - 1))
- }
-
- @Test
- fun `starting fetch with skipping one edition skips one edition`() {
- val fetchedSone = mock<Sone>()
- returnUriOnInsert(fetchedSone, currentEdition - 2)
- soneRescuer.startNextFetchWithSkip()
- soneRescuer.serviceRun()
- verify(core).lockSone(eq(sone))
- verify(core).updateSone(eq(fetchedSone), eq(true))
- assertThat(soneRescuer.isLastFetchSuccessful, equalTo(true))
- assertThat(soneRescuer.isFetching, equalTo(false))
- assertThat(soneRescuer.currentEdition, equalTo(currentEdition - 2))
- }
-
- @Test
- fun nonSuccessfulInsertIsRecognized() {
- returnUriOnInsert(null, (currentEdition - 1))
- soneRescuer.startNextFetch()
- soneRescuer.serviceRun()
- verify(core).lockSone(eq(sone))
- verify(core, never()).updateSone(any(Sone::class.java), eq(true))
- assertThat(soneRescuer.isLastFetchSuccessful, equalTo(false))
- assertThat(soneRescuer.isFetching, equalTo(false))
- assertThat(soneRescuer.currentEdition, equalTo(currentEdition))
- }
-
- private fun returnUriOnInsert(fetchedSone: Sone?, edition: Long) {
- val keyWithMetaStrings = setupFreenetUri(edition)
- doAnswer {
- soneRescuer.stop()
- fetchedSone
- }.whenever(soneDownloader).fetchSone(eq(sone), eq(keyWithMetaStrings), eq(true))
- }
-
- private fun setupFreenetUri(edition: Long): FreenetURI {
- val sskKey = mock<FreenetURI>()
- val keyWithDocName = mock<FreenetURI>()
- val keyWithMetaStrings = mock<FreenetURI>()
- whenever(keyWithDocName.setMetaString(eq(arrayOf("sone.xml")))).thenReturn(keyWithMetaStrings)
- whenever(sskKey.setDocName(eq("Sone-" + edition))).thenReturn(keyWithDocName)
- whenever(sone.requestUri.setKeyType(eq("SSK"))).thenReturn(sskKey)
- return keyWithMetaStrings
- }
-
-}
-
-private const val currentEdition = 12L
}
@Test
- fun `post request with fetch starts next fetch`() {
+ fun `post request with fetch and invalid edition starts next fetch`() {
setMethod(POST)
addHttpRequestPart("fetch", "true")
verifyRedirect("rescue.html") {
+ verify(soneRescuer, never()).setEdition(anyLong())
verify(soneRescuer).startNextFetch()
}
}
@Test
- fun `post request with skipping fetch starts next skipping fetch`() {
+ fun `post request with fetch and valid edition sets edition and starts next fetch`() {
setMethod(POST)
- addHttpRequestPart("fetchSkip", "true")
+ addHttpRequestPart("fetch", "true")
+ addHttpRequestPart("edition", "123")
+ verifyRedirect("rescue.html") {
+ verify(soneRescuer).setEdition(123L)
+ verify(soneRescuer).startNextFetch()
+ }
+ }
+
+ @Test
+ fun `post request with negative edition will not set edition`() {
+ setMethod(POST)
+ addHttpRequestPart("fetch", "true")
+ addHttpRequestPart("edition", "-123")
verifyRedirect("rescue.html") {
- verify(soneRescuer).startNextFetchWithSkip()
+ verify(soneRescuer, never()).setEdition(anyLong())
+ verify(soneRescuer).startNextFetch()
}
}