From: David ‘Bombe’ Roden Date: Fri, 1 Jul 2011 05:03:26 +0000 (+0200) Subject: Implement a better Sone Rescue Mode. X-Git-Tag: 0.6.6^2~41 X-Git-Url: https://git.pterodactylus.net/?a=commitdiff_plain;h=bb2c8cb2d47a77bcef2d299ad4bf8e16f22a6198;p=Sone.git Implement a better Sone Rescue Mode. --- diff --git a/src/main/java/net/pterodactylus/sone/core/Core.java b/src/main/java/net/pterodactylus/sone/core/Core.java index 7ecf0fa..ea36aad 100644 --- a/src/main/java/net/pterodactylus/sone/core/Core.java +++ b/src/main/java/net/pterodactylus/sone/core/Core.java @@ -49,7 +49,6 @@ 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.collection.Pair; import net.pterodactylus.util.config.Configuration; import net.pterodactylus.util.config.ConfigurationException; import net.pterodactylus.util.logging.Logging; @@ -60,7 +59,6 @@ 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.client.FetchResult; import freenet.keys.FreenetURI; /** @@ -127,6 +125,7 @@ public class Core implements IdentityListener, UpdateListener, SoneProvider, Pos private volatile FcpInterface fcpInterface; /** Whether the core has been stopped. */ + @SuppressWarnings("unused") private volatile boolean stopped; /** The Sones’ statuses. */ @@ -141,6 +140,10 @@ public class Core implements IdentityListener, UpdateListener, SoneProvider, Pos /* 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(); @@ -308,6 +311,26 @@ public class Core implements IdentityListener, UpdateListener, SoneProvider, Pos } /** + * 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 @@ -862,41 +885,7 @@ public class Core implements IdentityListener, UpdateListener, SoneProvider, Pos 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(); - /* find the latest edition the node knows about. */ - Pair currentUri = freenetInterface.fetchUri(sone.getRequestUri()); - if (currentUri != null) { - long currentEdition = currentUri.getLeft().getEdition(); - if (currentEdition > edition) { - edition = currentEdition; - } - } - 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; } } @@ -1064,14 +1053,28 @@ public class Core implements IdentityListener, UpdateListener, SoneProvider, Pos } /** - * 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 }); @@ -2371,29 +2374,6 @@ public class Core implements IdentityListener, UpdateListener, SoneProvider, Pos } /** - * 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! diff --git a/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java b/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java index 56c1e4a..0812f66 100644 --- a/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java +++ b/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java @@ -25,7 +25,6 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; -import net.pterodactylus.sone.core.Core.Preferences; import net.pterodactylus.sone.core.Core.SoneStatus; import net.pterodactylus.sone.data.Client; import net.pterodactylus.sone.data.Post; @@ -124,8 +123,7 @@ public class SoneDownloader extends AbstractService { /** * Fetches the updated Sone. This method can be used to fetch a Sone from a - * specific URI (which happens when {@link Preferences#isSoneRescueMode() - * „Sone rescue mode“} is active). + * specific URI. * * @param sone * The Sone to fetch @@ -133,6 +131,23 @@ public class SoneDownloader extends AbstractService { * The URI to fetch the Sone from */ public void fetchSone(Sone sone, FreenetURI soneUri) { + fetchSone(sone, soneUri, false); + } + + /** + * Fetches the Sone from the given URI. + * + * @param sone + * The Sone to fetch + * @param soneUri + * The URI of the Sone to fetch + * @param fetchOnly + * {@code true} to only fetch and parse the Sone, {@code false} + * to {@link Core#updateSone(Sone) update} it in the core + * @return The downloaded Sone, or {@code null} if the Sone could not be + * downloaded + */ + public Sone fetchSone(Sone sone, FreenetURI soneUri, boolean fetchOnly) { logger.log(Level.FINE, "Starting fetch for Sone “%s” from %s…", new Object[] { sone, soneUri }); FreenetURI requestUri = soneUri.setMetaString(new String[] { "sone.xml" }); core.setSoneStatus(sone, SoneStatus.downloading); @@ -140,14 +155,17 @@ public class SoneDownloader extends AbstractService { Pair fetchResults = freenetInterface.fetchUri(requestUri); if (fetchResults == null) { /* TODO - mark Sone as bad. */ - return; + return null; } logger.log(Level.FINEST, "Got %d bytes back.", fetchResults.getRight().size()); Sone parsedSone = parseSone(sone, fetchResults.getRight(), fetchResults.getLeft()); if (parsedSone != null) { - addSone(parsedSone); - core.updateSone(parsedSone); + if (!fetchOnly) { + core.updateSone(parsedSone); + addSone(parsedSone); + } } + return parsedSone; } finally { core.setSoneStatus(sone, (sone.getTime() == 0) ? SoneStatus.unknown : SoneStatus.idle); } diff --git a/src/main/java/net/pterodactylus/sone/core/SoneRescuer.java b/src/main/java/net/pterodactylus/sone/core/SoneRescuer.java new file mode 100644 index 0000000..542ec4a --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/core/SoneRescuer.java @@ -0,0 +1,173 @@ +/* + * Sone - SoneRescuer.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.core; + +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.util.service.AbstractService; +import freenet.keys.FreenetURI; + +/** + * The Sone rescuer downloads older editions of a Sone and updates the currently + * stored Sone with it. + * + * @author David ‘Bombe’ Roden + */ +public class SoneRescuer extends AbstractService { + + /** The core. */ + private final Core core; + + /** The Sone downloader. */ + private final SoneDownloader soneDownloader; + + /** The Sone being rescued. */ + private final Sone sone; + + /** Whether the rescuer is currently fetching a Sone. */ + private volatile boolean fetching; + + /** The currently tried edition. */ + private volatile long currentEdition; + + /** Whether the last fetch was successful. */ + private volatile boolean lastFetchSuccessful = true; + + /** + * Creates a new Sone rescuer. + * + * @param core + * The core + * @param soneDownloader + * The Sone downloader + * @param sone + * The Sone to rescue + */ + public SoneRescuer(Core core, SoneDownloader soneDownloader, Sone sone) { + super("Sone Rescuer for " + sone.getName()); + this.core = core; + this.soneDownloader = soneDownloader; + this.sone = sone; + currentEdition = sone.getRequestUri().getEdition(); + } + + // + // ACCESSORS + // + + /** + * Returns whether the Sone rescuer is currently fetching a Sone. + * + * @return {@code true} if the Sone rescuer is currently fetching a Sone + */ + public boolean isFetching() { + return fetching; + } + + /** + * Returns the edition that is currently being downloaded. + * + * @return The edition that is currently being downloaded + */ + public long getCurrentEdition() { + return currentEdition; + } + + /** + * Returns whether the Sone rescuer can download a next edition. + * + * @return {@code true} if the Sone rescuer can download a next edition, + * {@code false} if the last edition was already tried + */ + public boolean hasNextEdition() { + return currentEdition > 0; + } + + /** + * Returns the next edition the Sone rescuer can download. + * + * @return The next edition the Sone rescuer can download + */ + public long getNextEdition() { + return currentEdition - 1; + } + + /** + * 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} + * otherwise + */ + public boolean isLastFetchSuccessful() { + return lastFetchSuccessful; + } + + // + // ACTIONS + // + + /** + * 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(); + } + + // + // SERVICE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void serviceRun() { + while (!shouldStop()) { + while (!shouldStop() && !fetching) { + sleep(); + } + if (fetching) { + core.lockSone(sone); + 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); + } + fetching = false; + } + } + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/OptionsPage.java b/src/main/java/net/pterodactylus/sone/web/OptionsPage.java index 4f76fda..3d1eaad 100644 --- a/src/main/java/net/pterodactylus/sone/web/OptionsPage.java +++ b/src/main/java/net/pterodactylus/sone/web/OptionsPage.java @@ -108,8 +108,6 @@ public class OptionsPage extends SoneTemplatePage { Integer fcpFullAccessRequiredInteger = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("fcp-full-access-required", 1), preferences.getFcpFullAccessRequired().ordinal()); FullAccessRequired fcpFullAccessRequired = FullAccessRequired.values()[fcpFullAccessRequiredInteger]; preferences.setFcpFullAccessRequired(fcpFullAccessRequired); - boolean soneRescueMode = Boolean.parseBoolean(request.getHttpRequest().getPartAsStringFailsafe("sone-rescue-mode", 5)); - preferences.setSoneRescueMode(soneRescueMode); boolean clearOnNextRestart = Boolean.parseBoolean(request.getHttpRequest().getPartAsStringFailsafe("clear-on-next-restart", 5)); preferences.setClearOnNextRestart(clearOnNextRestart); boolean reallyClearOnNextRestart = Boolean.parseBoolean(request.getHttpRequest().getPartAsStringFailsafe("really-clear-on-next-restart", 5)); @@ -132,7 +130,6 @@ public class OptionsPage extends SoneTemplatePage { templateContext.set("trust-comment", preferences.getTrustComment()); templateContext.set("fcp-interface-active", preferences.isFcpInterfaceActive()); templateContext.set("fcp-full-access-required", preferences.getFcpFullAccessRequired().ordinal()); - templateContext.set("sone-rescue-mode", preferences.isSoneRescueMode()); templateContext.set("clear-on-next-restart", preferences.isClearOnNextRestart()); templateContext.set("really-clear-on-next-restart", preferences.isReallyClearOnNextRestart()); } diff --git a/src/main/java/net/pterodactylus/sone/web/RescuePage.java b/src/main/java/net/pterodactylus/sone/web/RescuePage.java new file mode 100644 index 0000000..40b03f1 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/RescuePage.java @@ -0,0 +1,72 @@ +/* + * Sone - RescuePage.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.core.SoneRescuer; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.web.page.Page.Request.Method; +import net.pterodactylus.util.number.Numbers; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; + +/** + * Page that lets the user control the rescue mode for a Sone. + * + * @see SoneRescuer + * @author David ‘Bombe’ Roden + */ +public class RescuePage extends SoneTemplatePage { + + /** + * Creates a new rescue page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public RescuePage(Template template, WebInterface webInterface) { + super("rescue.html", template, "Page.Rescue.Title", webInterface, true); + } + + // + // SONETEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException { + super.processTemplate(request, templateContext); + Sone currentSone = getCurrentSone(request.getToadletContext(), false); + SoneRescuer soneRescuer = webInterface.getCore().getSoneRescuer(currentSone); + if (request.getMethod() == Method.POST) { + if ("true".equals(request.getHttpRequest().getPartAsStringFailsafe("fetch", 4))) { + long edition = Numbers.safeParseLong(request.getHttpRequest().getPartAsStringFailsafe("edition", 8), -1L); + if (edition > -1) { + soneRescuer.setEdition(edition); + } + soneRescuer.startNextFetch(); + } + throw new RedirectException("rescue.html"); + } + templateContext.set("soneRescuer", soneRescuer); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/WebInterface.java b/src/main/java/net/pterodactylus/sone/web/WebInterface.java index 94a0fb6..6807b35 100644 --- a/src/main/java/net/pterodactylus/sone/web/WebInterface.java +++ b/src/main/java/net/pterodactylus/sone/web/WebInterface.java @@ -568,6 +568,7 @@ public class WebInterface implements CoreListener { Template deleteSoneTemplate = TemplateParser.parse(createReader("/templates/deleteSone.html")); Template noPermissionTemplate = TemplateParser.parse(createReader("/templates/noPermission.html")); Template optionsTemplate = TemplateParser.parse(createReader("/templates/options.html")); + Template rescueTemplate = TemplateParser.parse(createReader("/templates/rescue.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")); @@ -606,6 +607,7 @@ public class WebInterface implements CoreListener { pageToadlets.add(pageToadletFactory.createPageToadlet(new LoginPage(loginTemplate, this), "Login")); pageToadlets.add(pageToadletFactory.createPageToadlet(new LogoutPage(emptyTemplate, this), "Logout")); pageToadlets.add(pageToadletFactory.createPageToadlet(new OptionsPage(optionsTemplate, this), "Options")); + pageToadlets.add(pageToadletFactory.createPageToadlet(new RescuePage(rescueTemplate, this), "Rescue")); 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))); diff --git a/src/main/resources/i18n/sone.en.properties b/src/main/resources/i18n/sone.en.properties index bdca294..4bcd975 100644 --- a/src/main/resources/i18n/sone.en.properties +++ b/src/main/resources/i18n/sone.en.properties @@ -18,6 +18,8 @@ Navigation.Menu.Item.Logout.Name=Logout Navigation.Menu.Item.Logout.Tooltip=Logs you out of the current Sone Navigation.Menu.Item.Options.Name=Options Navigation.Menu.Item.Options.Tooltip=Options for the Sone plugin +Navigation.Menu.Item.Rescue.Name=Rescue +Navigation.Menu.Item.Rescue.Tooltip=Rescue Sone Navigation.Menu.Item.About.Name=About Navigation.Menu.Item.About.Tooltip=Information about Sone @@ -50,10 +52,6 @@ Page.Options.Option.FcpFullAccessRequired.Description=Require FCP connection fro Page.Options.Option.FcpFullAccessRequired.Value.No=No Page.Options.Option.FcpFullAccessRequired.Value.Writing=For Write Access Page.Options.Option.FcpFullAccessRequired.Value.Always=Always -Page.Options.Section.RescueOptions.Title=Rescue Settings -Page.Options.Option.SoneRescueMode.Description1=Try to rescue your Sones at the next start of the Sone plugin. The Rescue Mode will start at the latest known edition and will try to download all editions sequentially backwards, merging all discovered posts and replies together, until it is stopped or it has reached the first edition. -Page.Options.Option.SoneRescueMode.Description2=When using the Rescue Mode because Sone lost its configuration it usually suffices to let the Rescue Mode only run for a short time; use a second tab to control how many posts of your Sone are visible again. As soon as the last valid edition is loaded you can then deactivate the Rescue Mode. -Page.Options.Option.SoneRescueMode.Description3=Note that when you use the Rescue Mode posts that you have deleted after they have been inserted will have to be deleted again. Unfortunately this is an unavoidable side effect of the Rescue Mode. Page.Options.Section.Cleaning.Title=Clean Up Page.Options.Option.ClearOnNextRestart.Description=Resets the configuration of the Sone plugin at the next restart. Warning! {strong}This will destroy all of your Sones{/strong} so make sure you have backed up everyhing you still need! Also, you need to set the next option to true to actually do it. Page.Options.Option.ReallyClearOnNextRestart.Description=This option needs to be set to “yes” if you really, {strong}really{/strong} want to clear the plugin configuration on the next restart. @@ -197,6 +195,17 @@ Page.Search.Text.SoneHits=The following Sones match your search terms. Page.Search.Text.PostHits=The following posts match your search terms. Page.Search.Text.NoHits=No Sones or posts matched your search terms. +Page.Rescue.Title=Rescue Sone +Page.Rescue.Page.Title=Rescue Sone “{0}” +Page.Rescue.Text.Description=The Rescue Mode lets you restore previous versions of your Sone. This can be necessary if your configuration was lost. +Page.Rescue.Text.Procedure=The Rescue Mode works by fetching the latest inserted edition of your Sone. If an edition was successfully fetched it will be loaded into your Sone, letting you control your posts, profile, and other settings (you could do that in a second browser tab or window). If the fetched edition is not the one you want to restore, instruct the Rescue Mode to fetch the next older edition below. +Page.Rescue.Text.Fetching=The Sone Rescuer is currently fetching edition {0} of your Sone. +Page.Rescue.Text.Fetched=The Sone Rescuer has downloaded edition {0} of your Sone. Please check your posts, replies, and profile. If you like what the current Sone contains, just unlock it. +Page.Rescue.Text.FetchedLast=The Sone rescuer has downloaded the last available edition. If it did not manage to restore your Sone you are probably out of luck now. +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.NoPermission.Title=Unauthorized Access - Sone Page.NoPermission.Page.Title=Unauthorized Access Page.NoPermission.Text.NoPermission=You tried to do something that you do not have sufficient authorization for. Please refrain from such actions in the future or we will be forced to take counter-measures! diff --git a/src/main/resources/templates/options.html b/src/main/resources/templates/options.html index 2bf98fd..cd1ff2f 100644 --- a/src/main/resources/templates/options.html +++ b/src/main/resources/templates/options.html @@ -98,13 +98,6 @@

-

<%= Page.Options.Section.RescueOptions.Title|l10n|html>

- -

<%= Page.Options.Option.SoneRescueMode.Description1|l10n|html>

-

<%= Page.Options.Option.SoneRescueMode.Description2|l10n|html>

-

<%= Page.Options.Option.SoneRescueMode.Description3|l10n|html>

-

-

<%= Page.Options.Section.Cleaning.Title|l10n|html>

<%= Page.Options.Option.ClearOnNextRestart.Description|l10n|html|replace needle="{strong}" replacement=""|replace needle="{/strong}" replacement="">

diff --git a/src/main/resources/templates/rescue.html b/src/main/resources/templates/rescue.html new file mode 100644 index 0000000..e08edc3 --- /dev/null +++ b/src/main/resources/templates/rescue.html @@ -0,0 +1,32 @@ +<%include include/head.html> + +

<%= Page.Rescue.Page.Title|l10n 0=currentSone.niceName|html>

+ +

<%= Page.Rescue.Text.Description|l10n|html>

+

<%= Page.Rescue.Text.Procedure|l10n|html>

+ +<%if soneRescuer.fetching> +

<%= Page.Rescue.Text.Fetching|l10n 0=soneRescuer.currentEdition|html>

+<%else> + <%if soneRescuer.hasNextEdition> + <%if soneRescuer.lastFetchSuccessful> +

<%= Page.Rescue.Text.Fetched|l10n 0=soneRescuer.currentEdition|html>

+ <%else> +

<%= Page.Rescue.Text.NotFetched|l10n 0=soneRescuer.currentEdition|html>

+ <%/if> +
+ + + + +
+ <%else> + <%if soneRescuer.lastFetchSuccessful> +

<%= Page.Rescue.Text.Fetched|l10n 0=soneRescuer.currentEdition|html>

+ <%else> +

<%= Page.Rescue.Text.FetchedLast|l10n|html>

+ <%/if> + <%/if> +<%/if> + +<%include include/tail.html>