<modelVersion>4.0.0</modelVersion>
<groupId>net.pterodactylus</groupId>
<artifactId>sone</artifactId>
- <version>0.3.2</version>
+ <version>0.3.4</version>
<dependencies>
<dependency>
<groupId>net.pterodactylus</groupId>
*/
public void lockSone(Sone sone) {
synchronized (lockedSones) {
- lockedSones.add(sone);
+ if (lockedSones.add(sone)) {
+ coreListenerManager.fireSoneLocked(sone);
+ }
}
}
*/
public void unlockSone(Sone sone) {
synchronized (lockedSones) {
- lockedSones.remove(sone);
+ if (lockedSones.remove(sone)) {
+ coreListenerManager.fireSoneUnlocked(sone);
+ }
}
}
* @param sone
* The Sone to save
*/
- public void saveSone(Sone sone) {
+ public synchronized void saveSone(Sone sone) {
if (!isLocalSone(sone)) {
logger.log(Level.FINE, "Tried to save non-local Sone: %s", sone);
return;
}
configuration.getStringValue(sonePrefix + "/Friends/" + friendCounter + "/ID").setValue(null);
+ configuration.save();
logger.log(Level.INFO, "Sone %s saved.", sone);
} catch (ConfigurationException ce1) {
logger.log(Level.WARNING, "Could not save Sone: " + sone, ce1);
/**
* Saves the current options.
*/
- public void saveConfiguration() {
+ public synchronized void saveConfiguration() {
/* store the options first. */
try {
configuration.getIntValue("Option/InsertionDelay").setValue(options.getIntegerOption("InsertionDelay").getReal());
*/
public void replyRemoved(Reply reply);
+ /**
+ * Notifies a listener when a Sone was locked.
+ *
+ * @param sone
+ * The Sone that was locked
+ */
+ public void soneLocked(Sone sone);
+
+ /**
+ * Notifies a listener that a Sone was unlocked.
+ *
+ * @param sone
+ * The Sone that was unlocked
+ */
+ public void soneUnlocked(Sone sone);
+
}
}
}
+ /**
+ * Notifies all listeners that the given Sone was locked.
+ *
+ * @see CoreListener#soneLocked(Sone)
+ * @param sone
+ * The Sone that was locked
+ */
+ void fireSoneLocked(Sone sone) {
+ for (CoreListener coreListener : getListeners()) {
+ coreListener.soneLocked(sone);
+ }
+ }
+
+ /**
+ * Notifies all listeners that the given Sone was unlocked.
+ *
+ * @see CoreListener#soneUnlocked(Sone)
+ * @param sone
+ * The Sone that was unlocked
+ */
+ void fireSoneUnlocked(Sone sone) {
+ for (CoreListener coreListener : getListeners()) {
+ coreListener.soneUnlocked(sone);
+ }
+ }
+
}
soneProperties.put("posts", new ArrayList<Post>(sone.getPosts()));
soneProperties.put("replies", new HashSet<Reply>(sone.getReplies()));
soneProperties.put("likedPostIds", new HashSet<String>(sone.getLikedPostIds()));
- soneProperties.put("likeReplyIds", new HashSet<String>(sone.getLikedReplyIds()));
+ soneProperties.put("likedReplyIds", new HashSet<String>(sone.getLikedReplyIds()));
}
//
import freenet.pluginmanager.FredPluginThreadless;
import freenet.pluginmanager.FredPluginVersioned;
import freenet.pluginmanager.PluginRespirator;
-import freenet.pluginmanager.PluginStore;
/**
* This class interfaces with Freenet. It is the class that is loaded by the
}
/** The version. */
- public static final Version VERSION = new Version(0, 3, 2);
+ public static final Version VERSION = new Version(0, 3, 4);
/** The logger. */
private static final Logger logger = Logging.getLogger(SonePlugin.class);
/** The l10n helper. */
private PluginL10n l10n;
- /** The plugin store. */
- private PluginStore pluginStore;
-
/** The identity manager. */
private IdentityManager identityManager;
/* create a configuration. */
Configuration oldConfiguration;
Configuration newConfiguration = null;
+ boolean firstStart = !new File("sone.properties").exists();
+ boolean newConfig = false;
try {
oldConfiguration = new Configuration(new MapConfigurationBackend(new File("sone.properties"), false));
newConfiguration = oldConfiguration;
} catch (ConfigurationException ce1) {
+ newConfig = true;
logger.log(Level.INFO, "Could not load configuration file, trying plugin store…", ce1);
try {
newConfiguration = new Configuration(new MapConfigurationBackend(new File("sone.properties"), true));
core.setConfiguration(newConfiguration);
}
webInterface.start();
+ webInterface.setFirstStart(firstStart);
+ webInterface.setNewConfig(newConfig);
identityManager.start();
startupFailed = false;
} finally {
/* stop the identity manager. */
identityManager.stop();
-
- /* TODO wait for core to stop? */
- try {
- pluginRespirator.putStore(pluginStore);
- } catch (DatabaseDisabledException dde1) {
- logger.log(Level.WARNING, "Could not store plugin store, database is disabled.", dde1);
- }
-
+ } catch (Throwable t1) {
+ logger.log(Level.SEVERE, "Error while shutting down!", t1);
} finally {
/* shutdown logger. */
Logging.shutdown();
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import net.pterodactylus.sone.web.page.PageToadletFactory;
import net.pterodactylus.sone.web.page.StaticPage;
import net.pterodactylus.util.logging.Logging;
+import net.pterodactylus.util.notify.Notification;
import net.pterodactylus.util.notify.NotificationManager;
import net.pterodactylus.util.notify.TemplateNotification;
import net.pterodactylus.util.template.DateFilter;
/** The “Sone rescued” notification. */
private final ListNotification<Sone> sonesRescuedNotification;
+ /** Sone locked notification ticker objects. */
+ private final Map<Sone, Object> lockedSonesTickerObjects = Collections.synchronizedMap(new HashMap<Sone, Object>());
+
+ /** The “Sone locked” notification. */
+ private final ListNotification<Sone> lockedSonesNotification;
+
/**
* Creates a new web interface.
*
Template sonesRescuedTemplate = templateFactory.createTemplate(createReader("/templates/notify/sonesRescuedNotification.html"));
sonesRescuedNotification = new ListNotification<Sone>("sones-rescued-notification", "sones", sonesRescuedTemplate);
+
+ Template lockedSonesTemplate = templateFactory.createTemplate(createReader("/templates/notify/lockedSonesNotification.html"));
+ lockedSonesNotification = new ListNotification<Sone>("sones-locked-notification", "sones", lockedSonesTemplate);
}
//
return new HashSet<Reply>(newReplyNotification.getElements());
}
+ /**
+ * Sets whether the current start of the plugin is the first start. It is
+ * considered a first start if the configuration file does not exist.
+ *
+ * @param firstStart
+ * {@code true} if no configuration file existed when Sone was
+ * loaded, {@code false} otherwise
+ */
+ public void setFirstStart(boolean firstStart) {
+ if (firstStart) {
+ Template firstStartNotificationTemplate = templateFactory.createTemplate(createReader("/templates/notify/firstStartNotification.html"));
+ Notification firstStartNotification = new TemplateNotification("first-start-notification", firstStartNotificationTemplate);
+ notificationManager.addNotification(firstStartNotification);
+ }
+ }
+
+ /**
+ * Sets whether Sone was started with a fresh configuration file.
+ *
+ * @param newConfig
+ * {@code true} if Sone was started with a fresh configuration,
+ * {@code false} if the existing configuration could be read
+ */
+ public void setNewConfig(boolean newConfig) {
+ if (newConfig && !hasFirstStartNotification()) {
+ Template configNotReadNotificationTemplate = templateFactory.createTemplate(createReader("/templates/notify/configNotReadNotification.html"));
+ Notification configNotReadNotification = new TemplateNotification("config-not-read-notification", configNotReadNotificationTemplate);
+ notificationManager.addNotification(configNotReadNotification);
+ }
+ }
+
+ //
+ // PRIVATE ACCESSORS
+ //
+
+ /**
+ * Returns whether the first start notification is currently displayed.
+ *
+ * @return {@code true} if the first-start notification is currently
+ * displayed, {@code false} otherwise
+ */
+ private boolean hasFirstStartNotification() {
+ return notificationManager.getNotification("first-start-notification") != null;
+ }
+
//
// ACTIONS
//
@Override
public void newSoneFound(Sone sone) {
newSoneNotification.add(sone);
- notificationManager.addNotification(newSoneNotification);
+ if (!hasFirstStartNotification()) {
+ notificationManager.addNotification(newSoneNotification);
+ }
}
/**
@Override
public void newPostFound(Post post) {
newPostNotification.add(post);
- notificationManager.addNotification(newPostNotification);
+ if (!hasFirstStartNotification()) {
+ notificationManager.addNotification(newPostNotification);
+ } else {
+ getCore().markPostKnown(post);
+ }
}
/**
return;
}
newReplyNotification.add(reply);
- notificationManager.addNotification(newReplyNotification);
+ if (!hasFirstStartNotification()) {
+ notificationManager.addNotification(newReplyNotification);
+ } else {
+ getCore().markReplyKnown(reply);
+ }
}
/**
*/
@Override
public void postRemoved(Post post) {
- /* TODO */
+ newPostNotification.remove(post);
}
/**
*/
@Override
public void replyRemoved(Reply reply) {
- /* TODO */
+ newReplyNotification.remove(reply);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void soneLocked(final Sone sone) {
+ Object tickerObject = Ticker.getInstance().registerEvent(System.currentTimeMillis() + (5 * 60) * 1000, new Runnable() {
+
+ @Override
+ @SuppressWarnings("synthetic-access")
+ public void run() {
+ lockedSonesNotification.add(sone);
+ notificationManager.addNotification(lockedSonesNotification);
+ }
+ }, "Sone Locked Notification");
+ lockedSonesTickerObjects.put(sone, tickerObject);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void soneUnlocked(Sone sone) {
+ Object tickerObject = lockedSonesTickerObjects.remove(sone);
+ if (tickerObject == null) {
+ return;
+ }
+ lockedSonesNotification.remove(sone);
+ Ticker.getInstance().deregisterEvent(tickerObject);
}
/**
import java.io.StringWriter;
import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.web.WebInterface;
import net.pterodactylus.util.io.Closer;
import net.pterodactylus.util.json.JsonObject;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
import net.pterodactylus.util.template.TemplateException;
if (post == null) {
return createErrorJsonObject("invalid-post-id");
}
- postTemplate.set("currentSone", getCurrentSone(request.getToadletContext()));
- return createSuccessJsonObject().put("post", createJsonPost(post));
+ return createSuccessJsonObject().put("post", createJsonPost(post, getCurrentSone(request.getToadletContext())));
}
/**
*
* @param post
* The post to create a JSON object from
+ * @param currentSone
+ * The currently logged in Sone (to store in the template)
* @return The JSON representation of the post
*/
- private JsonObject createJsonPost(Post post) {
+ private JsonObject createJsonPost(Post post, Sone currentSone) {
JsonObject jsonPost = new JsonObject();
jsonPost.put("id", post.getId());
jsonPost.put("sone", post.getSone().getId());
jsonPost.put("recipient", (post.getRecipient() == null) ? null : post.getRecipient().getId());
jsonPost.put("time", post.getTime());
- postTemplate.set("post", post);
StringWriter stringWriter = new StringWriter();
+ DataProvider dataProvider = postTemplate.createDataProvider();
+ dataProvider.setData("post", post);
+ dataProvider.setData("currentSone", currentSone);
try {
- postTemplate.render(stringWriter);
+ postTemplate.render(dataProvider, stringWriter);
} catch (TemplateException te1) {
/* TODO - shouldn’t happen. */
} finally {
import java.io.StringWriter;
import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.web.WebInterface;
import net.pterodactylus.util.io.Closer;
import net.pterodactylus.util.json.JsonObject;
+import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.Template;
import net.pterodactylus.util.template.TemplateException;
if ((reply == null) || (reply.getSone() == null)) {
return createErrorJsonObject("invalid-reply-id");
}
- replyTemplate.set("currentSone", getCurrentSone(request.getToadletContext()));
- return createSuccessJsonObject().put("reply", createJsonReply(reply));
+ return createSuccessJsonObject().put("reply", createJsonReply(reply, getCurrentSone(request.getToadletContext())));
}
/**
*
* @param reply
* The reply to convert
+ * @param currentSone
+ * The currently logged in Sone (to store in the template)
* @return The JSON representation of the reply
*/
- private JsonObject createJsonReply(Reply reply) {
+ private JsonObject createJsonReply(Reply reply, Sone currentSone) {
JsonObject jsonReply = new JsonObject();
jsonReply.put("id", reply.getId());
jsonReply.put("postId", reply.getPost().getId());
jsonReply.put("soneId", reply.getSone().getId());
jsonReply.put("time", reply.getTime());
- replyTemplate.set("reply", reply);
StringWriter stringWriter = new StringWriter();
+ DataProvider dataProvider = replyTemplate.createDataProvider();
+ dataProvider.setData("reply", reply);
+ dataProvider.setData("currentSone", currentSone);
try {
- replyTemplate.render(stringWriter);
+ replyTemplate.render(dataProvider, stringWriter);
} catch (TemplateException te1) {
/* TODO - shouldn’t happen. */
} finally {
WebInterface.SelectBox.No=No
WebInterface.ClickToShow.Replies=Click here to show hidden replies.
+Notification.FirstStart.Text=This seems to be the first time you start Sone. To start, create a new Sone from a web of trust identity and start following other Sones.
Notification.Startup.Text=Sone is currently starting up. It may take a while to retrieve all identities and Sones from the web of trust. If you are missing some elements, please be patient, they will probably reappear very soon.
+Notification.ConfigNotRead.Text=The configuration file “sone.properties” could not be read, probably because it was not saved correctly. This can happen on versions prior to Sone 0.3.3 and there is nothing you can do about it.
Notification.Button.Dismiss=Dismiss
Notification.NewSone.Text=New Sones have been discovered:
Notification.NewPost.Text=New posts have been discovered by the following Sones:
Notification.SoneIsBeingRescued.Text=The following Sones are currently being rescued:
Notification.SoneRescued.Text=The following Sones have been rescued:
Notification.SoneRescued.Text.RememberToUnlock=Please remember to control the posts and replies you have given and don’t forget to unlock your Sones!
-
+Notification.LockedSones.Text=The following Sones have been locked for more than 5 minutes. Please check if you really want to keep these Sones locked:
--- /dev/null
+<div class="text"><%= Notification.ConfigNotRead.Text|l10n|html></div>
--- /dev/null
+<div class="text"><%= Notification.FirstStart.Text|l10n|html></div>
--- /dev/null
+<div class="text">
+ <%= Notification.LockedSones.Text|l10n|html>
+ <%foreach sones sone>
+ <a href="viewSone.html?sone=<% sone.id|html>" title="<% sone.requestUri|html>"><% sone.niceName|html></a><%notlast>,<%/notlast><%last>.<%/last>
+ <%/foreach>
+</div>