X-Git-Url: https://git.pterodactylus.net/?p=Sone.git;a=blobdiff_plain;f=src%2Fmain%2Fjava%2Fnet%2Fpterodactylus%2Fsone%2Fcore%2FCore.java;h=284c6fb97ab803a8b31110c6d5b3366ef88b3f28;hp=1a1c6df5dfedc63fec66604c0866b85c3ddde3a8;hb=2c5b76a2fbce16ef33f079a3de9ae9fc9a9d30b8;hpb=fd4e3c59035c9321a63507e2100c66532c8f1938 diff --git a/src/main/java/net/pterodactylus/sone/core/Core.java b/src/main/java/net/pterodactylus/sone/core/Core.java index 1a1c6df..284c6fb 100644 --- a/src/main/java/net/pterodactylus/sone/core/Core.java +++ b/src/main/java/net/pterodactylus/sone/core/Core.java @@ -1,5 +1,5 @@ /* - * FreenetSone - Core.java - Copyright © 2010 David Roden + * Sone - Core.java - Copyright © 2010 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 @@ -17,37 +17,31 @@ package net.pterodactylus.sone.core; -import java.io.InputStream; import java.net.MalformedURLException; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; -import java.util.Comparator; 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 java.util.logging.Logger; import net.pterodactylus.sone.core.Options.DefaultOption; import net.pterodactylus.sone.core.Options.Option; import net.pterodactylus.sone.core.Options.OptionWatcher; -import net.pterodactylus.sone.core.SoneException.Type; import net.pterodactylus.sone.data.Post; -import net.pterodactylus.sone.data.Profile; import net.pterodactylus.sone.data.Reply; import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.freenet.wot.WebOfTrustConnector; +import net.pterodactylus.sone.freenet.wot.Identity; +import net.pterodactylus.sone.freenet.wot.IdentityListener; +import net.pterodactylus.sone.freenet.wot.IdentityManager; +import net.pterodactylus.sone.freenet.wot.OwnIdentity; import net.pterodactylus.util.config.Configuration; import net.pterodactylus.util.config.ConfigurationException; -import net.pterodactylus.util.filter.Filter; -import net.pterodactylus.util.filter.Filters; import net.pterodactylus.util.logging.Logging; -import net.pterodactylus.util.service.AbstractService; -import freenet.client.FetchResult; +import net.pterodactylus.util.number.Numbers; import freenet.keys.FreenetURI; /** @@ -55,17 +49,7 @@ import freenet.keys.FreenetURI; * * @author David ‘Bombe’ Roden */ -public class Core extends AbstractService { - - /** The default Sones. */ - private static final Set defaultSones = new HashSet(); - - static { - /* Sone of Sone. */ - defaultSones.add("USK@eRHt0ceFsHjRZ11j6dd68RSdIvfd8f9YjJLZ9lnhEyo,iJWjIWh6TkMZm1NY8qBranKTIuwsCPkVPG6T6c6ft-I,AQACAAE/Sone/4"); - /* Sone of Bombe. */ - defaultSones.add("USK@RuW~uAO35Ipne896-1OmaVJNPuYE4ZIB5oZ5ziaU57A,7rV3uiyztXBDt03DCoRiNwiGjgFCJuznM9Okc1opURU,AQACAAE/Sone/29"); - } +public class Core implements IdentityListener { /** * Enumeration for the possible states of a {@link Sone}. @@ -94,42 +78,54 @@ public class Core extends AbstractService { private final Options options = new Options(); /** The configuration. */ - private Configuration configuration; + private final Configuration configuration; - /** Interface to freenet. */ - private FreenetInterface freenetInterface; + /** The identity manager. */ + private final IdentityManager identityManager; - /** The WoT connector. */ - private WebOfTrustConnector webOfTrustConnector; + /** Interface to freenet. */ + private final FreenetInterface freenetInterface; /** The Sone downloader. */ - private SoneDownloader soneDownloader; + private final SoneDownloader soneDownloader; - /** The local Sones. */ - private final Set localSones = Collections.synchronizedSet(new HashSet()); + /** The Sones’ statuses. */ + /* synchronize access on itself. */ + private final Map soneStatuses = new HashMap(); /** Sone inserters. */ - private final Map soneInserters = Collections.synchronizedMap(new HashMap()); - - /** The Sones’ statuses. */ - private final Map soneStatuses = Collections.synchronizedMap(new HashMap()); + /* synchronize access on this on localSones. */ + private final Map soneInserters = new HashMap(); - /* various caches follow here. */ + /** All local Sones. */ + /* synchronize access on this on itself. */ + private Map localSones = new HashMap(); - /** Cache for all known Sones. */ - private final Map soneCache = Collections.synchronizedMap(new HashMap()); + /** All remote Sones. */ + /* synchronize access on this on itself. */ + private Map remoteSones = new HashMap(); - /** Cache for all known posts. */ - private final Map postCache = Collections.synchronizedMap(new HashMap()); + /** All posts. */ + private Map posts = new HashMap(); - /** Cache for all known replies. */ - private final Map replyCache = Collections.synchronizedMap(new HashMap()); + /** All replies. */ + private Map replies = new HashMap(); /** * Creates a new core. + * + * @param configuration + * The configuration of the core + * @param freenetInterface + * The freenet interface + * @param identityManager + * The identity manager */ - public Core() { - super("Sone Core", false); + public Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager) { + this.configuration = configuration; + this.freenetInterface = freenetInterface; + this.identityManager = identityManager; + this.soneDownloader = new SoneDownloader(this, freenetInterface); } // @@ -137,190 +133,234 @@ public class Core extends AbstractService { // /** - * Returns the options of the Sone plugin. + * Returns the options used by the core. * - * @return The options of the Sone plugin + * @return The options of the core */ public Options getOptions() { return options; } /** - * Sets the configuration of the core. + * Returns the identity manager used by the core. * - * @param configuration - * The configuration of the core - * @return This core (for method chaining) + * @return The identity manager */ - public Core configuration(Configuration configuration) { - this.configuration = configuration; - return this; + public IdentityManager getIdentityManager() { + return identityManager; } /** - * Sets the Freenet interface to use. + * Returns the status of the given Sone. * - * @param freenetInterface - * The Freenet interface to use - * @return This core (for method chaining) + * @param sone + * The Sone to get the status for + * @return The status of the Sone */ - public Core freenetInterface(FreenetInterface freenetInterface) { - this.freenetInterface = freenetInterface; - soneDownloader = new SoneDownloader(this, freenetInterface); - soneDownloader.start(); - return this; + public SoneStatus getSoneStatus(Sone sone) { + synchronized (soneStatuses) { + return soneStatuses.get(sone); + } } /** - * Returns the Web of Trust connector. + * Sets the status of the given Sone. * - * @return The Web of Trust connector + * @param sone + * The Sone to set the status of + * @param soneStatus + * The status to set */ - public WebOfTrustConnector getWebOfTrustConnector() { - return webOfTrustConnector; + public void setSoneStatus(Sone sone, SoneStatus soneStatus) { + synchronized (soneStatuses) { + soneStatuses.put(sone, soneStatus); + } } /** - * Sets the Web of Trust connector. + * Returns all Sones, remote and local. * - * @param webOfTrustConnector - * The Web of Trust connector - * @return This core (for method chaining) + * @return All Sones */ - public Core setWebOfTrustConnector(WebOfTrustConnector webOfTrustConnector) { - this.webOfTrustConnector = webOfTrustConnector; - return this; + public Set getSones() { + Set allSones = new HashSet(); + allSones.addAll(getLocalSones()); + allSones.addAll(getRemoteSones()); + return allSones; } /** - * Returns the local Sones. + * Returns the Sone with the given ID, regardless whether it’s local or + * remote. * - * @return The local Sones + * @param id + * The ID of the Sone to get + * @return The Sone with the given ID, or {@code null} if there is no such + * Sone + */ + public Sone getSone(String id) { + Sone sone = getRemoteSone(id); + if (sone != null) { + return sone; + } + sone = getLocalSone(id); + return sone; + } + + /** + * Returns whether the given Sone is a local Sone. + * + * @param sone + * The Sone to check for its locality + * @return {@code true} if the given Sone is local, {@code false} otherwise */ - public Set getSones() { - return Collections.unmodifiableSet(localSones); + public boolean isLocalSone(Sone sone) { + synchronized (localSones) { + return localSones.containsKey(sone.getId()); + } } /** - * Returns the Sone with the given ID, or an empty Sone that has been - * initialized with the given ID. + * Returns all local Sones. * - * @param soneId - * The ID of the Sone - * @return The Sone + * @return All local Sones */ - public Sone getSone(String soneId) { - if (!soneCache.containsKey(soneId)) { - Sone sone = new Sone(soneId); - soneCache.put(soneId, sone); - setSoneStatus(sone, SoneStatus.unknown); + public Set getLocalSones() { + synchronized (localSones) { + return new HashSet(localSones.values()); } - return soneCache.get(soneId); } /** - * Returns all known sones. + * Returns the local Sone with the given ID. * - * @return All known sones + * @param id + * The ID of the Sone to get + * @return The Sone, or {@code null} if there is no Sone with the given ID */ - public Collection getKnownSones() { - return Collections.unmodifiableCollection(soneCache.values()); + public Sone getLocalSone(String id) { + synchronized (localSones) { + return localSones.get(id); + } } /** - * Gets all known Sones that are not local Sones. + * Returns all remote Sones. * * @return All remote Sones */ - public Collection getRemoteSones() { - return Collections.unmodifiableCollection(getKnownSones()); + public Set getRemoteSones() { + synchronized (remoteSones) { + return new HashSet(remoteSones.values()); + } } /** - * Returns the status of the given Sone. + * Returns the remote Sone with the given ID. * - * @param sone - * The Sone to get the status for - * @return The status of the Sone + * @param id + * The ID of the remote Sone to get + * @return The Sone, or {@code null} if there is no such Sone */ - public SoneStatus getSoneStatus(Sone sone) { - return soneStatuses.get(sone); + public Sone getRemoteSone(String id) { + synchronized (remoteSones) { + return remoteSones.get(id); + } } /** - * Sets the status of the Sone. + * Returns whether the given Sone is a remote Sone. * * @param sone - * The Sone to set the status for - * @param soneStatus - * The status of the Sone + * The Sone to check + * @return {@code true} if the given Sone is a remote Sone, {@code false} + * otherwise */ - public void setSoneStatus(Sone sone, SoneStatus soneStatus) { - soneStatuses.put(sone, soneStatus); + public boolean isRemoteSone(Sone sone) { + synchronized (remoteSones) { + return remoteSones.containsKey(sone.getId()); + } } /** - * Creates a new post and adds it to the given Sone. + * Returns the post with the given ID. * - * @param sone - * The sone that creates the post - * @param text - * The text of the post - * @return The created post + * @param postId + * The ID of the post to get + * @return The post, or {@code null} if there is no such post */ - public Post createPost(Sone sone, String text) { - return createPost(sone, System.currentTimeMillis(), text); + public Post getPost(String postId) { + synchronized (posts) { + return posts.get(postId); + } } /** - * Creates a new post and adds it to the given Sone. + * Returns the reply with the given ID. * - * @param sone - * The Sone that creates the post - * @param time - * The time of the post - * @param text - * The text of the post - * @return The created post + * @param replyId + * The ID of the reply to get + * @return The reply, or {@code null} if there is no such reply */ - public Post createPost(Sone sone, long time, String text) { - Post post = getPost(UUID.randomUUID().toString()).setSone(sone).setTime(time).setText(text); - sone.addPost(post); - return post; + public Reply getReply(String replyId) { + synchronized (replies) { + return replies.get(replyId); + } } /** - * Creates a reply. + * Returns all replies for the given post, order ascending by time. * - * @param sone - * The Sone that posts the reply * @param post - * The post the reply refers to - * @param text - * The text of the reply - * @return The created reply + * The post to get all replies for + * @return All replies for the given post */ - public Reply createReply(Sone sone, Post post, String text) { - return createReply(sone, post, System.currentTimeMillis(), text); + public List getReplies(Post post) { + Set sones = getSones(); + List replies = new ArrayList(); + for (Sone sone : sones) { + for (Reply reply : sone.getReplies()) { + if (reply.getPost().equals(post)) { + replies.add(reply); + } + } + } + Collections.sort(replies, Reply.TIME_COMPARATOR); + return replies; } /** - * Creates a reply. + * Returns all Sones that have liked the given post. * - * @param sone - * The Sone that posts the reply * @param post - * The post the reply refers to - * @param time - * The time of the post - * @param text - * The text of the reply - * @return The created reply - */ - public Reply createReply(Sone sone, Post post, long time, String text) { - Reply reply = getReply(UUID.randomUUID().toString()).setSone(sone).setPost(post).setTime(time).setText(text); - sone.addReply(reply); - return reply; + * The post to get the liking Sones for + * @return The Sones that like the given post + */ + public Set getLikes(Post post) { + Set sones = new HashSet(); + for (Sone sone : getSones()) { + if (sone.getLikedPostIds().contains(post.getId())) { + sones.add(sone); + } + } + return sones; + } + + /** + * Returns all Sones that have liked the given reply. + * + * @param reply + * The reply to get the liking Sones for + * @return The Sones that like the given reply + */ + public Set getLikes(Reply reply) { + Set sones = new HashSet(); + for (Sone sone : getSones()) { + if (sone.getLikedPostIds().contains(reply.getId())) { + sones.add(sone); + } + } + return sones; } // @@ -328,334 +368,306 @@ public class Core extends AbstractService { // /** - * Adds a Sone to watch for updates. The Sone needs to be completely - * initialized. + * Adds a local Sone from the given ID which has to be the ID of an own + * identity. * - * @param sone - * The Sone to watch for updates - */ - public void addSone(Sone sone) { - soneCache.put(sone.getId(), sone); - if (!localSones.contains(sone)) { - soneDownloader.addSone(sone); + * @param id + * The ID of an own identity to add a Sone for + * @return The added (or already existing) Sone + */ + public Sone addLocalSone(String id) { + synchronized (localSones) { + if (localSones.containsKey(id)) { + logger.log(Level.FINE, "Tried to add known local Sone: %s", id); + return localSones.get(id); + } + OwnIdentity ownIdentity = identityManager.getOwnIdentity(id); + if (ownIdentity == null) { + logger.log(Level.INFO, "Invalid Sone ID: %s", id); + return null; + } + return addLocalSone(ownIdentity); } } /** - * Adds the given Sone. + * Adds a local Sone from the given own identity. * - * @param sone - * The Sone to add + * @param ownIdentity + * The own identity to create a Sone from + * @return The added (or already existing) Sone */ - public void addLocalSone(Sone sone) { - if (localSones.add(sone)) { - setSoneStatus(sone, SoneStatus.idle); + public Sone addLocalSone(OwnIdentity ownIdentity) { + if (ownIdentity == null) { + logger.log(Level.WARNING, "Given OwnIdentity is null!"); + return null; + } + synchronized (localSones) { + if (localSones.containsKey(ownIdentity.getId())) { + logger.log(Level.FINE, "Tried to add known local Sone: %s", ownIdentity); + return localSones.get(ownIdentity.getId()); + } + String latestEdition = ownIdentity.getProperty("Sone.LatestEdition"); + Sone sone = new Sone(ownIdentity).setInsertUri(getSoneUri(ownIdentity.getInsertUri(), latestEdition)).setRequestUri(getSoneUri(ownIdentity.getRequestUri(), latestEdition)); + /* TODO - load posts ’n stuff */ + localSones.put(ownIdentity.getId(), sone); SoneInserter soneInserter = new SoneInserter(this, freenetInterface, sone); - soneInserter.start(); soneInserters.put(sone, soneInserter); + soneInserter.start(); + setSoneStatus(sone, SoneStatus.idle); + return sone; } } /** - * Creates a new Sone at a random location. + * Creates a new Sone for the given own identity. * - * @param name - * The name of the Sone + * @param ownIdentity + * The own identity to create a Sone for * @return The created Sone - * @throws SoneException - * if a Sone error occurs */ - public Sone createSone(String name) throws SoneException { - return createSone(name, "Sone", null, null); - } - - /** - * Creates a new Sone at the given location. If one of {@code requestUri} or - * {@code insertUrI} is {@code null}, the Sone is created at a random - * location. - * - * @param name - * The name of the Sone - * @param documentName - * The document name in the SSK - * @param requestUri - * The request URI of the Sone, or {@link NullPointerException} - * to create a Sone at a random location - * @param insertUri - * The insert URI of the Sone, or {@code null} to create a Sone - * at a random location - * @return The created Sone - * @throws SoneException - * if a Sone error occurs - */ - public Sone createSone(String name, String documentName, String requestUri, String insertUri) throws SoneException { - if ((name == null) || (name.trim().length() == 0)) { - throw new SoneException(Type.INVALID_SONE_NAME); - } - String finalRequestUri; - String finalInsertUri; - if ((requestUri == null) || (insertUri == null)) { - String[] keyPair = freenetInterface.generateKeyPair(); - finalRequestUri = keyPair[0]; - finalInsertUri = keyPair[1]; - } else { - finalRequestUri = requestUri; - finalInsertUri = insertUri; - } - Sone sone; - try { - logger.log(Level.FINEST, "Creating new Sone “%s” at %s (%s)…", new Object[] { name, finalRequestUri, finalInsertUri }); - sone = getSone(UUID.randomUUID().toString()).setName(name).setRequestUri(new FreenetURI(finalRequestUri).setKeyType("USK").setDocName(documentName)).setInsertUri(new FreenetURI(finalInsertUri).setKeyType("USK").setDocName(documentName)); - sone.setProfile(new Profile()); - /* set modification counter to 1 so it is inserted immediately. */ - sone.setModificationCounter(1); - addLocalSone(sone); - } catch (MalformedURLException mue1) { - throw new SoneException(Type.INVALID_URI); + public Sone createSone(OwnIdentity ownIdentity) { + identityManager.addContext(ownIdentity, "Sone"); + Sone sone = addLocalSone(ownIdentity); + synchronized (sone) { + /* mark as modified so that it gets inserted immediately. */ + sone.setModificationCounter(sone.getModificationCounter() + 1); } return sone; } /** - * Loads the Sone from the given request URI. The fetching of the data is - * performed in a new thread so this method returns immediately. + * Adds the Sone of the given identity. * - * @param requestUri - * The request URI to load the Sone from + * @param identity + * The identity whose Sone to add + * @return The added or already existing Sone */ - public void loadSone(final String requestUri) { - loadSone(requestUri, null); + public Sone addRemoteSone(Identity identity) { + if (identity == null) { + logger.log(Level.WARNING, "Given Identity is null!"); + return null; + } + synchronized (remoteSones) { + if (remoteSones.containsKey(identity.getId())) { + logger.log(Level.FINE, "Identity already exists: %s", identity); + return remoteSones.get(identity.getId()); + } + Sone sone = new Sone(identity); + sone.setRequestUri(getSoneUri(identity.getRequestUri(), identity.getProperty("Sone.LatestEdition"))); + remoteSones.put(identity.getId(), sone); + soneDownloader.addSone(sone); + setSoneStatus(sone, SoneStatus.idle); + return sone; + } } /** - * Loads the Sone from the given request URI. The fetching of the data is - * performed in a new thread so this method returns immediately. If - * {@code insertUri} is not {@code null} the loaded Sone is converted into a - * local Sone and available using as any other local Sone. + * Updates the stores Sone with the given Sone. * - * @param requestUri - * The request URI to load the Sone from - * @param insertUri - * The insert URI of the Sone - */ - public void loadSone(final String requestUri, final String insertUri) { - new Thread(new Runnable() { - - @Override - @SuppressWarnings("synthetic-access") - public void run() { - try { - FreenetURI realRequestUri = new FreenetURI(requestUri).setMetaString(new String[] { "sone.xml" }); - FetchResult fetchResult = freenetInterface.fetchUri(realRequestUri); - if (fetchResult == null) { - return; - } - Sone parsedSone = soneDownloader.parseSone(null, fetchResult, realRequestUri); - if (parsedSone != null) { - if (insertUri != null) { - parsedSone.setInsertUri(new FreenetURI(insertUri)); - addLocalSone(parsedSone); - } else { - addSone(parsedSone); - } - setSoneStatus(parsedSone, SoneStatus.idle); - } - } catch (MalformedURLException mue1) { - logger.log(Level.INFO, "Could not create URI from “" + requestUri + "”.", mue1); + * @param sone + * The updated Sone + */ + public void updateSone(Sone sone) { + if (isRemoteSone(sone)) { + Sone storedSone = getRemoteSone(sone.getId()); + if (!(sone.getTime() > storedSone.getTime())) { + logger.log(Level.FINE, "Downloaded Sone %s is not newer than stored Sone %s.", new Object[] { sone, storedSone }); + return; + } + synchronized (posts) { + for (Post post : storedSone.getPosts()) { + posts.remove(post.getId()); } + for (Post post : sone.getPosts()) { + posts.put(post.getId(), post); + } + } + synchronized (replies) { + for (Reply reply : storedSone.getReplies()) { + replies.remove(reply.getId()); + } + for (Reply reply : sone.getReplies()) { + replies.put(reply.getId(), reply); + } + } + synchronized (storedSone) { + storedSone.setTime(sone.getTime()); + storedSone.setProfile(sone.getProfile()); + storedSone.setPosts(sone.getPosts()); + storedSone.setReplies(sone.getReplies()); + storedSone.setLikePostIds(sone.getLikedPostIds()); + storedSone.setLikeReplyIds(sone.getLikedReplyIds()); + storedSone.updateUris(sone.getRequestUri().getEdition()); } - }, "Sone Downloader").start(); + saveSone(storedSone); + } } /** - * Loads a Sone from an input stream. + * Deletes the given Sone. This will remove the Sone from the + * {@link #getLocalSone(String) local Sones}, stops its {@link SoneInserter} + * and remove the context from its identity. * - * @param soneInputStream - * The input stream to load the Sone from - * @return The parsed Sone, or {@code null} if the Sone could not be parsed + * @param sone + * The Sone to delete */ - public Sone loadSone(InputStream soneInputStream) { - Sone parsedSone = soneDownloader.parseSone(soneInputStream); - if (parsedSone == null) { - return null; + public void deleteSone(Sone sone) { + if (!(sone.getIdentity() instanceof OwnIdentity)) { + logger.log(Level.WARNING, "Tried to delete Sone of non-own identity: %s", sone); + return; } - if (parsedSone.getInsertUri() != null) { - addLocalSone(parsedSone); - } else { - addSone(parsedSone); + synchronized (localSones) { + if (!localSones.containsKey(sone.getId())) { + logger.log(Level.WARNING, "Tried to delete non-local Sone: %s", sone); + return; + } + localSones.remove(sone.getId()); + soneInserters.remove(sone.getId()).stop(); } - return parsedSone; + identityManager.removeContext((OwnIdentity) sone.getIdentity(), "Sone"); } /** - * Loads and updates the given Sone. + * Saves the given Sone. This will persist all local settings for the given + * Sone, such as the friends list and similar, private options. * * @param sone - * The Sone to load + * The Sone to save */ - public void loadSone(final Sone sone) { - new Thread(new Runnable() { - - @Override - @SuppressWarnings("synthetic-access") - public void run() { - FreenetURI realRequestUri = sone.getRequestUri().setMetaString(new String[] { "sone.xml" }); - setSoneStatus(sone, SoneStatus.downloading); - try { - FetchResult fetchResult = freenetInterface.fetchUri(realRequestUri); - if (fetchResult == null) { - /* TODO - mark Sone as bad. */ - return; - } - Sone parsedSone = soneDownloader.parseSone(sone, fetchResult, realRequestUri); - if (parsedSone != null) { - addSone(parsedSone); - } - } finally { - setSoneStatus(sone, (sone.getTime() == 0) ? SoneStatus.unknown : SoneStatus.idle); - } - } - }, "Sone Downloader").start(); + public void saveSone(Sone sone) { + if (!isLocalSone(sone)) { + logger.log(Level.FINE, "Tried to save non-local Sone: %s", sone); + } + /* TODO - implement saving. */ } /** - * Deletes the given Sone from this plugin instance. + * Creates a new post. * * @param sone - * The sone to delete + * The Sone that creates the post + * @param text + * The text of the post */ - public void deleteSone(Sone sone) { - SoneInserter soneInserter = soneInserters.remove(sone); - soneInserter.stop(); - localSones.remove(sone); - soneStatuses.remove(sone); - soneCache.remove(sone.getId()); + public void createPost(Sone sone, String text) { + createPost(sone, System.currentTimeMillis(), text); } /** - * Returns the post with the given ID. If no post exists yet with the given - * ID, a new post is returned. + * Creates a new post. * - * @param postId - * The ID of the post - * @return The post + * @param sone + * The Sone that creates the post + * @param time + * The time of the post + * @param text + * The text of the post */ - public Post getPost(String postId) { - if (!postCache.containsKey(postId)) { - postCache.put(postId, new Post(postId)); + public void createPost(Sone sone, long time, String text) { + if (!isLocalSone(sone)) { + logger.log(Level.FINE, "Tried to create post for non-local Sone: %s", sone); + return; } - return postCache.get(postId); - } - - /** - * Returns the reply with the given ID. If no reply exists yet with the - * given ID, a new reply is returned. - * - * @param replyId - * The ID of the reply - * @return The reply - */ - public Reply getReply(String replyId) { - if (!replyCache.containsKey(replyId)) { - replyCache.put(replyId, new Reply(replyId)); + Post post = new Post(sone, time, text); + synchronized (posts) { + posts.put(post.getId(), post); } - return replyCache.get(replyId); + sone.addPost(post); + saveSone(sone); } /** - * Gets all replies to the given post, sorted by date, oldest first. + * Deletes the given post. * * @param post - * The post the replies refer to - * @return The sorted list of replies for the post + * The post to delete */ - public List getReplies(Post post) { - List replies = new ArrayList(); - for (Reply reply : replyCache.values()) { - if (reply.getPost().equals(post)) { - replies.add(reply); - } + public void deletePost(Post post) { + if (!isLocalSone(post.getSone())) { + logger.log(Level.WARNING, "Tried to delete post of non-local Sone: %s", post.getSone()); + return; } - Collections.sort(replies, new Comparator() { - - /** - * {@inheritDoc} - */ - @Override - public int compare(Reply leftReply, Reply rightReply) { - return (int) Math.max(Integer.MIN_VALUE, Math.min(Integer.MAX_VALUE, leftReply.getTime() - rightReply.getTime())); - } - }); - return replies; + post.getSone().removePost(post); + synchronized (posts) { + posts.remove(post.getId()); + } + saveSone(post.getSone()); } /** - * Gets all Sones that like the given post. + * Creates a new reply. * + * @param sone + * The Sone that creates the reply * @param post - * The post to check for - * @return All Sones that like the post + * The post that this reply refers to + * @param text + * The text of the reply */ - public Collection getLikes(final Post post) { - return Filters.filteredCollection(getKnownSones(), new Filter() { - - @Override - public boolean filterObject(Sone sone) { - return sone.isLikedPostId(post.getId()); - } - }); + public void createReply(Sone sone, Post post, String text) { + createReply(sone, post, System.currentTimeMillis(), text); } /** - * Gets all Sones that like the given reply. + * Creates a new reply. * - * @param reply - * The reply to check for - * @return All Sones that like the reply + * @param sone + * The Sone that creates the reply + * @param post + * The post that this reply refers to + * @param time + * The time of the reply + * @param text + * The text of the reply */ - public Collection getLikes(final Reply reply) { - return Filters.filteredCollection(getKnownSones(), new Filter() { - - @Override - public boolean filterObject(Sone sone) { - return sone.isLikedReplyId(reply.getId()); - } - }); + public void createReply(Sone sone, Post post, long time, String text) { + if (!isLocalSone(sone)) { + logger.log(Level.FINE, "Tried to create reply for non-local Sone: %s", sone); + return; + } + Reply reply = new Reply(sone, post, System.currentTimeMillis(), text); + synchronized (replies) { + replies.put(reply.getId(), reply); + } + sone.addReply(reply); + saveSone(sone); } /** - * Deletes the given reply. It is removed from its Sone and from the reply - * cache. + * Deletes the given reply. * * @param reply - * The reply to remove + * The reply to delete */ public void deleteReply(Reply reply) { - reply.getSone().removeReply(reply); - replyCache.remove(reply.getId()); + Sone sone = reply.getSone(); + if (!isLocalSone(sone)) { + logger.log(Level.FINE, "Tried to delete non-local reply: %s", reply); + return; + } + synchronized (replies) { + replies.remove(reply.getId()); + } + sone.removeReply(reply); + saveSone(sone); } - // - // SERVICE METHODS - // - /** - * {@inheritDoc} + * Starts the core. */ - @Override - protected void serviceStart() { + public void start() { loadConfiguration(); } /** - * {@inheritDoc} + * Stops the core. */ - @Override - protected void serviceStop() { - soneDownloader.stop(); - /* stop all Sone inserters. */ - for (SoneInserter soneInserter : soneInserters.values()) { - soneInserter.stop(); + public void stop() { + synchronized (localSones) { + for (SoneInserter soneInserter : soneInserters.values()) { + soneInserter.stop(); + } } saveConfiguration(); } @@ -665,32 +677,11 @@ public class Core extends AbstractService { // /** - * Adds some default Sones. - */ - private void addDefaultSones() { - for (String soneUri : defaultSones) { - loadSone(soneUri); - } - } - - /** * Loads the configuration. */ @SuppressWarnings("unchecked") private void loadConfiguration() { - logger.entering(Core.class.getName(), "loadConfiguration()"); - - boolean firstStart = configuration.getBooleanValue("FirstStart").getValue(true); - if (firstStart) { - logger.log(Level.INFO, "First start of Sone, adding a couple of default Sones…"); - addDefaultSones(); - try { - configuration.getBooleanValue("FirstStart").setValue(false); - } catch (ConfigurationException ce1) { - logger.log(Level.WARNING, "Could not clear “first start” flag!"); - } - } - + /* create options. */ options.addIntegerOption("InsertionDelay", new DefaultOption(60, new OptionWatcher() { @Override @@ -699,14 +690,10 @@ public class Core extends AbstractService { } })); - options.addBooleanOption("ClearOnNextRestart", new DefaultOption(false)); options.addBooleanOption("ReallyClearOnNextRestart", new DefaultOption(false)); - if (firstStart) { - return; - } - + /* read options from configuration. */ options.getBooleanOption("ClearOnNextRestart").set(configuration.getBooleanValue("Option/ClearOnNextRestart").getValue(null)); options.getBooleanOption("ReallyClearOnNextRestart").set(configuration.getBooleanValue("Option/ReallyClearOnNextRestart").getValue(null)); boolean clearConfiguration = options.getBooleanOption("ClearOnNextRestart").get() && options.getBooleanOption("ReallyClearOnNextRestart").get(); @@ -714,203 +701,92 @@ public class Core extends AbstractService { options.getBooleanOption("ReallyClearOnNextRestart").set(null); if (clearConfiguration) { /* stop loading the configuration. */ - addDefaultSones(); return; } options.getIntegerOption("InsertionDelay").set(configuration.getIntValue("Option/InsertionDelay").getValue(null)); - /* parse local Sones. */ - logger.log(Level.INFO, "Loading Sones…"); - int soneId = 0; - do { - String sonePrefix = "Sone/Sone." + soneId++; - String id = configuration.getStringValue(sonePrefix + "/ID").getValue(null); - if (id == null) { - break; - } - String name = configuration.getStringValue(sonePrefix + "/Name").getValue(null); - long time = configuration.getLongValue(sonePrefix + "/Time").getValue((long) 0); - String insertUri = configuration.getStringValue(sonePrefix + "/InsertURI").getValue(null); - String requestUri = configuration.getStringValue(sonePrefix + "/RequestURI").getValue(null); - long modificationCounter = configuration.getLongValue(sonePrefix + "/ModificationCounter").getValue((long) 0); - String firstName = configuration.getStringValue(sonePrefix + "/Profile/FirstName").getValue(null); - String middleName = configuration.getStringValue(sonePrefix + "/Profile/MiddleName").getValue(null); - String lastName = configuration.getStringValue(sonePrefix + "/Profile/LastName").getValue(null); - Integer birthDay = configuration.getIntValue(sonePrefix + "/Profile/BirthDay").getValue(null); - Integer birthMonth = configuration.getIntValue(sonePrefix + "/Profile/BirthMonth").getValue(null); - Integer birthYear = configuration.getIntValue(sonePrefix + "/Profile/BirthYear").getValue(null); - try { - Profile profile = new Profile(); - profile.setFirstName(firstName); - profile.setMiddleName(middleName); - profile.setLastName(lastName); - profile.setBirthDay(birthDay).setBirthMonth(birthMonth).setBirthYear(birthYear); - Sone sone = getSone(id).setName(name).setTime(time).setRequestUri(new FreenetURI(requestUri)).setInsertUri(new FreenetURI(insertUri)); - sone.setProfile(profile); - int postId = 0; - do { - String postPrefix = sonePrefix + "/Post." + postId++; - id = configuration.getStringValue(postPrefix + "/ID").getValue(null); - if (id == null) { - break; - } - time = configuration.getLongValue(postPrefix + "/Time").getValue((long) 0); - String text = configuration.getStringValue(postPrefix + "/Text").getValue(null); - Post post = getPost(id).setSone(sone).setTime(time).setText(text); - sone.addPost(post); - } while (true); - int replyCounter = 0; - do { - String replyPrefix = sonePrefix + "/Reply." + replyCounter++; - String replyId = configuration.getStringValue(replyPrefix + "/ID").getValue(null); - if (replyId == null) { - break; - } - Post replyPost = getPost(configuration.getStringValue(replyPrefix + "/Post").getValue(null)); - long replyTime = configuration.getLongValue(replyPrefix + "/Time").getValue(null); - String replyText = configuration.getStringValue(replyPrefix + "/Text").getValue(null); - Reply reply = getReply(replyId).setSone(sone).setPost(replyPost).setTime(replyTime).setText(replyText); - sone.addReply(reply); - } while (true); - - /* load friends. */ - int friendCounter = 0; - while (true) { - String friendPrefix = sonePrefix + "/Friend." + friendCounter++; - String friendId = configuration.getStringValue(friendPrefix + "/ID").getValue(null); - if (friendId == null) { - break; - } - Sone friendSone = getSone(friendId); - String friendKey = configuration.getStringValue(friendPrefix + "/Key").getValue(null); - String friendName = configuration.getStringValue(friendPrefix + "/Name").getValue(null); - friendSone.setRequestUri(new FreenetURI(friendKey)).setName(friendName); - sone.addFriend(friendSone); - } - - /* load liked post IDs. */ - int likedPostIdCounter = 0; - while (true) { - String likedPostIdPrefix = sonePrefix + "/LikedPostId." + likedPostIdCounter++; - String likedPostId = configuration.getStringValue(likedPostIdPrefix + "/ID").getValue(null); - if (likedPostId == null) { - break; - } - sone.addLikedPostId(likedPostId); - } - - /* load liked reply IDs. */ - int likedReplyIdCounter = 0; - while (true) { - String likedReplyIdPrefix = sonePrefix + "/LikedReplyId." + likedReplyIdCounter++; - String likedReplyId = configuration.getStringValue(likedReplyIdPrefix + "/ID").getValue(null); - if (likedReplyId == null) { - break; - } - sone.addLikedReplyId(likedReplyId); - } - - sone.setModificationCounter(modificationCounter); - addLocalSone(sone); - } catch (MalformedURLException mue1) { - logger.log(Level.WARNING, "Could not create Sone from requestUri (“" + requestUri + "”) and insertUri (“" + insertUri + "”)!", mue1); - } - } while (true); - logger.log(Level.INFO, "Loaded %d Sones.", getSones().size()); - - /* load all remote Sones. */ - for (Sone remoteSone : getRemoteSones()) { - loadSone(remoteSone); - } - - logger.exiting(Core.class.getName(), "loadConfiguration()"); } /** - * Saves the configuraiton. + * Saves the current options. */ private void saveConfiguration() { - Set sones = getSones(); - logger.log(Level.INFO, "Storing %d Sones…", sones.size()); - + /* store the options first. */ try { - /* store the options first. */ configuration.getIntValue("Option/InsertionDelay").setValue(options.getIntegerOption("InsertionDelay").getReal()); configuration.getBooleanValue("Option/ClearOnNextRestart").setValue(options.getBooleanOption("ClearOnNextRestart").getReal()); configuration.getBooleanValue("Option/ReallyClearOnNextRestart").setValue(options.getBooleanOption("ReallyClearOnNextRestart").getReal()); + } catch (ConfigurationException ce1) { + logger.log(Level.SEVERE, "Could not store configuration!", ce1); + } + } - /* store all Sones. */ - int soneId = 0; - for (Sone sone : localSones) { - String sonePrefix = "Sone/Sone." + soneId++; - configuration.getStringValue(sonePrefix + "/ID").setValue(sone.getId()); - configuration.getStringValue(sonePrefix + "/Name").setValue(sone.getName()); - configuration.getLongValue(sonePrefix + "/Time").setValue(sone.getTime()); - configuration.getStringValue(sonePrefix + "/RequestURI").setValue(sone.getRequestUri().toString()); - configuration.getStringValue(sonePrefix + "/InsertURI").setValue(sone.getInsertUri().toString()); - configuration.getLongValue(sonePrefix + "/ModificationCounter").setValue(sone.getModificationCounter()); - Profile profile = sone.getProfile(); - configuration.getStringValue(sonePrefix + "/Profile/FirstName").setValue(profile.getFirstName()); - configuration.getStringValue(sonePrefix + "/Profile/MiddleName").setValue(profile.getMiddleName()); - configuration.getStringValue(sonePrefix + "/Profile/LastName").setValue(profile.getLastName()); - configuration.getIntValue(sonePrefix + "/Profile/BirthDay").setValue(profile.getBirthDay()); - configuration.getIntValue(sonePrefix + "/Profile/BirthMonth").setValue(profile.getBirthMonth()); - configuration.getIntValue(sonePrefix + "/Profile/BirthYear").setValue(profile.getBirthYear()); - int postId = 0; - for (Post post : sone.getPosts()) { - String postPrefix = sonePrefix + "/Post." + postId++; - configuration.getStringValue(postPrefix + "/ID").setValue(post.getId()); - configuration.getLongValue(postPrefix + "/Time").setValue(post.getTime()); - configuration.getStringValue(postPrefix + "/Text").setValue(post.getText()); - } - /* write null ID as terminator. */ - configuration.getStringValue(sonePrefix + "/Post." + postId + "/ID").setValue(null); + /** + * Generate a Sone URI from the given URI and latest edition. + * + * @param uriString + * The URI to derive the Sone URI from + * @param latestEditionString + * The latest edition as a {@link String}, or {@code null} + * @return The derived URI + */ + private FreenetURI getSoneUri(String uriString, String latestEditionString) { + try { + FreenetURI uri = new FreenetURI(uriString).setDocName("Sone").setMetaString(new String[0]).setSuggestedEdition(Numbers.safeParseLong(latestEditionString, (long) 0)); + return uri; + } catch (MalformedURLException mue1) { + logger.log(Level.WARNING, "Could not create Sone URI from URI: " + uriString, mue1); + return null; + } + } - int replyId = 0; - for (Reply reply : sone.getReplies()) { - String replyPrefix = sonePrefix + "/Reply." + replyId++; - configuration.getStringValue(replyPrefix + "/ID").setValue(reply.getId()); - configuration.getStringValue(replyPrefix + "/Post").setValue(reply.getPost().getId()); - configuration.getLongValue(replyPrefix + "/Time").setValue(reply.getTime()); - configuration.getStringValue(replyPrefix + "/Text").setValue(reply.getText()); - } - /* write null ID as terminator. */ - configuration.getStringValue(sonePrefix + "/Reply." + replyId + "/ID").setValue(null); - - int friendId = 0; - for (Sone friend : sone.getFriends()) { - String friendPrefix = sonePrefix + "/Friend." + friendId++; - configuration.getStringValue(friendPrefix + "/ID").setValue(friend.getId()); - configuration.getStringValue(friendPrefix + "/Key").setValue(friend.getRequestUri().toString()); - configuration.getStringValue(friendPrefix + "/Name").setValue(friend.getName()); - } - /* write null ID as terminator. */ - configuration.getStringValue(sonePrefix + "/Friend." + friendId + "/ID").setValue(null); - - /* write all liked posts. */ - int likedPostIdCounter = 0; - for (String soneLikedPostId : sone.getLikedPostIds()) { - String likedPostIdPrefix = sonePrefix + "/LikedPostId." + likedPostIdCounter++; - configuration.getStringValue(likedPostIdPrefix + "/ID").setValue(soneLikedPostId); - } - configuration.getStringValue(sonePrefix + "/LikedPostId." + likedPostIdCounter + "/ID").setValue(null); + // + // INTERFACE IdentityListener + // - /* write all liked replies. */ - int likedReplyIdCounter = 0; - for (String soneLikedReplyId : sone.getLikedReplyIds()) { - String likedReplyIdPrefix = sonePrefix + "/LikedReplyId." + likedReplyIdCounter++; - configuration.getStringValue(likedReplyIdPrefix + "/ID").setValue(soneLikedReplyId); - } - configuration.getStringValue(sonePrefix + "/LikedReplyId." + likedReplyIdCounter + "/ID").setValue(null); + /** + * {@inheritDoc} + */ + @Override + public void ownIdentityAdded(OwnIdentity ownIdentity) { + logger.log(Level.FINEST, "Adding OwnIdentity: " + ownIdentity); + if (ownIdentity.hasContext("Sone")) { + addLocalSone(ownIdentity); + } + } - } - /* write null ID as terminator. */ - configuration.getStringValue("Sone/Sone." + soneId + "/ID").setValue(null); + /** + * {@inheritDoc} + */ + @Override + public void ownIdentityRemoved(OwnIdentity ownIdentity) { + /* TODO */ + } - } catch (ConfigurationException ce1) { - logger.log(Level.WARNING, "Could not store configuration!", ce1); - } + /** + * {@inheritDoc} + */ + @Override + public void identityAdded(Identity identity) { + logger.log(Level.FINEST, "Adding Identity: " + identity); + addRemoteSone(identity); + } + + /** + * {@inheritDoc} + */ + @Override + public void identityUpdated(Identity identity) { + /* TODO */ + } + + /** + * {@inheritDoc} + */ + @Override + public void identityRemoved(Identity identity) { + /* TODO */ } }