Implement a better Sone Rescue Mode.
[Sone.git] / src / main / java / net / pterodactylus / sone / core / SoneDownloader.java
index 0cedc15..0812f66 100644 (file)
@@ -55,6 +55,9 @@ public class SoneDownloader extends AbstractService {
        /** The logger. */
        private static final Logger logger = Logging.getLogger(SoneDownloader.class);
 
+       /** The maximum protocol version. */
+       private static final int MAX_PROTOCOL_VERSION = 0;
+
        /** The core. */
        private final Core core;
 
@@ -89,9 +92,10 @@ public class SoneDownloader extends AbstractService {
         *            The Sone to add
         */
        public void addSone(Sone sone) {
-               if (sones.add(sone)) {
-                       freenetInterface.registerUsk(sone, this);
+               if (!sones.add(sone)) {
+                       freenetInterface.unregisterUsk(sone);
                }
+               freenetInterface.registerUsk(sone, this);
        }
 
        /**
@@ -114,13 +118,12 @@ public class SoneDownloader extends AbstractService {
         *            The Sone to fetch
         */
        public void fetchSone(Sone sone) {
-               fetchSone(sone, sone.getRequestUri());
+               fetchSone(sone, sone.getRequestUri().sskForUSK());
        }
 
        /**
         * Fetches the updated Sone. This method can be used to fetch a Sone from a
-        * specific URI (which happens when {@link Core#isSoneRescueMode() „Sone
-        * rescue mode“} is active).
+        * specific URI.
         *
         * @param sone
         *            The Sone to fetch
@@ -128,9 +131,23 @@ public class SoneDownloader extends AbstractService {
         *            The URI to fetch the Sone from
         */
        public void fetchSone(Sone sone, FreenetURI soneUri) {
-               if (core.getSoneStatus(sone) == SoneStatus.downloading) {
-                       return;
-               }
+               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);
@@ -138,13 +155,17 @@ public class SoneDownloader extends AbstractService {
                        Pair<FreenetURI, FetchResult> 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) {
-                               core.updateSone(parsedSone);
+                               if (!fetchOnly) {
+                                       core.updateSone(parsedSone);
+                                       addSone(parsedSone);
+                               }
                        }
+                       return parsedSone;
                } finally {
                        core.setSoneStatus(sone, (sone.getTime() == 0) ? SoneStatus.unknown : SoneStatus.idle);
                }
@@ -221,6 +242,27 @@ public class SoneDownloader extends AbstractService {
                        return null;
                }
 
+               Integer protocolVersion = null;
+               String soneProtocolVersion = soneXml.getValue("protocol-version", null);
+               if (soneProtocolVersion != null) {
+                       protocolVersion = Numbers.safeParseInteger(soneProtocolVersion);
+               }
+               if (protocolVersion == null) {
+                       logger.log(Level.INFO, "No protocol version found, assuming 0.");
+                       protocolVersion = 0;
+               }
+
+               if (protocolVersion < 0) {
+                       logger.log(Level.WARNING, "Invalid protocol version: " + protocolVersion + "! Not parsing Sone.");
+                       return null;
+               }
+
+               /* check for valid versions. */
+               if (protocolVersion > MAX_PROTOCOL_VERSION) {
+                       logger.log(Level.WARNING, "Unknown protocol version: " + protocolVersion + "! Not parsing Sone.");
+                       return null;
+               }
+
                String soneTime = soneXml.getValue("time", null);
                if (soneTime == null) {
                        /* TODO - mark Sone as bad. */
@@ -261,7 +303,7 @@ public class SoneDownloader extends AbstractService {
                if ((soneInsertUri != null) && (sone.getInsertUri() == null)) {
                        try {
                                sone.setInsertUri(new FreenetURI(soneInsertUri));
-                               sone.setLatestEdition(Math.max(sone.getRequestUri().getSuggestedEdition(), sone.getInsertUri().getSuggestedEdition()));
+                               sone.setLatestEdition(Math.max(sone.getRequestUri().getEdition(), sone.getInsertUri().getEdition()));
                        } catch (MalformedURLException mue1) {
                                /* TODO - mark Sone as bad. */
                                logger.log(Level.WARNING, "Downloaded Sone " + sone + " has invalid insert URI: " + soneInsertUri, mue1);
@@ -286,6 +328,25 @@ public class SoneDownloader extends AbstractService {
                Profile profile = new Profile().setFirstName(profileFirstName).setMiddleName(profileMiddleName).setLastName(profileLastName);
                profile.setBirthDay(profileBirthDay).setBirthMonth(profileBirthMonth).setBirthYear(profileBirthYear);
 
+               /* parse profile fields. */
+               SimpleXML profileFieldsXml = profileXml.getNode("fields");
+               if (profileFieldsXml != null) {
+                       for (SimpleXML fieldXml : profileFieldsXml.getNodes("field")) {
+                               String fieldName = fieldXml.getValue("field-name", null);
+                               String fieldValue = fieldXml.getValue("field-value", null);
+                               if ((fieldName == null) || (fieldValue == null)) {
+                                       logger.log(Level.WARNING, "Downloaded profile field for Sone %s with missing data! Name: %s, Value: %s", new Object[] { sone, fieldName, fieldValue });
+                                       return null;
+                               }
+                               try {
+                                       profile.addField(fieldName).setValue(fieldValue);
+                               } catch (IllegalArgumentException iae1) {
+                                       logger.log(Level.WARNING, "Duplicate field: " + fieldName, iae1);
+                                       return null;
+                               }
+                       }
+               }
+
                /* parse posts. */
                SimpleXML postsXml = soneXml.getNode("posts");
                Set<Post> posts = new HashSet<Post>();
@@ -295,6 +356,7 @@ public class SoneDownloader extends AbstractService {
                } else {
                        for (SimpleXML postXml : postsXml.getNodes("post")) {
                                String postId = postXml.getValue("id", null);
+                               String postRecipientId = postXml.getValue("recipient", null);
                                String postTime = postXml.getValue("time", null);
                                String postText = postXml.getValue("text", null);
                                if ((postId == null) || (postTime == null) || (postText == null)) {
@@ -303,7 +365,11 @@ public class SoneDownloader extends AbstractService {
                                        return null;
                                }
                                try {
-                                       posts.add(core.getPost(postId).setSone(sone).setTime(Long.parseLong(postTime)).setText(postText));
+                                       Post post = core.getPost(postId).setSone(sone).setTime(Long.parseLong(postTime)).setText(postText);
+                                       if ((postRecipientId != null) && (postRecipientId.length() == 43)) {
+                                               post.setRecipient(core.getSone(postRecipientId));
+                                       }
+                                       posts.add(post);
                                } catch (NumberFormatException nfe1) {
                                        /* TODO - mark Sone as bad. */
                                        logger.log(Level.WARNING, "Downloaded post for Sone %s with invalid time: %s", new Object[] { sone, postTime });