Merge branch 'release-0.3.6.3'
[Sone.git] / src / main / java / net / pterodactylus / sone / core / SoneDownloader.java
index d84a1c4..4328f02 100644 (file)
@@ -26,10 +26,12 @@ import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import net.pterodactylus.sone.core.Core.SoneStatus;
+import net.pterodactylus.sone.data.Client;
 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.util.collection.Pair;
 import net.pterodactylus.util.io.Closer;
 import net.pterodactylus.util.logging.Logging;
 import net.pterodactylus.util.number.Numbers;
@@ -53,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;
 
@@ -112,20 +117,34 @@ public class SoneDownloader extends AbstractService {
         *            The Sone to fetch
         */
        public void fetchSone(Sone sone) {
+               fetchSone(sone, sone.getRequestUri());
+       }
+
+       /**
+        * 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).
+        *
+        * @param sone
+        *            The Sone to fetch
+        * @param soneUri
+        *            The URI to fetch the Sone from
+        */
+       public void fetchSone(Sone sone, FreenetURI soneUri) {
                if (core.getSoneStatus(sone) == SoneStatus.downloading) {
                        return;
                }
-               logger.log(Level.FINE, "Starting fetch for Sone “%s” from %s…", new Object[] { sone, sone.getRequestUri().setMetaString(new String[] { "sone.xml" }) });
-               FreenetURI requestUri = sone.getRequestUri().setMetaString(new String[] { "sone.xml" });
+               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);
                try {
-                       FetchResult fetchResult = freenetInterface.fetchUri(requestUri);
-                       if (fetchResult == null) {
+                       Pair<FreenetURI, FetchResult> fetchResults = freenetInterface.fetchUri(requestUri);
+                       if (fetchResults == null) {
                                /* TODO - mark Sone as bad. */
                                return;
                        }
-                       logger.log(Level.FINEST, "Got %d bytes back.", fetchResult.size());
-                       Sone parsedSone = parseSone(sone, fetchResult, requestUri);
+                       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);
                        }
@@ -146,14 +165,19 @@ public class SoneDownloader extends AbstractService {
         * @return The parsed Sone, or {@code null} if the Sone could not be parsed
         */
        public Sone parseSone(Sone originalSone, FetchResult fetchResult, FreenetURI requestUri) {
-               logger.log(Level.FINEST, "Persing FetchResult (%d bytes, %s) for %s…", new Object[] { fetchResult.size(), fetchResult.getMimeType(), originalSone });
+               logger.log(Level.FINEST, "Parsing FetchResult (%d bytes, %s) for %s…", new Object[] { fetchResult.size(), fetchResult.getMimeType(), originalSone });
                Bucket soneBucket = fetchResult.asBucket();
                InputStream soneInputStream = null;
                try {
                        soneInputStream = soneBucket.getInputStream();
                        Sone parsedSone = parseSone(originalSone, soneInputStream);
                        if (parsedSone != null) {
-                               parsedSone.setRequestUri(requestUri.setMetaString(new String[0]));
+                               parsedSone.setLatestEdition(requestUri.getEdition());
+                               if (requestUri.getKeyType().equals("USK")) {
+                                       parsedSone.setRequestUri(requestUri.setMetaString(new String[0]));
+                               } else {
+                                       parsedSone.setRequestUri(requestUri.setKeyType("USK").setDocName("Sone").setMetaString(new String[0]));
+                               }
                        }
                        return parsedSone;
                } catch (IOException ioe1) {
@@ -166,16 +190,16 @@ public class SoneDownloader extends AbstractService {
        }
 
        /**
-        * Parses a Sone from the given input stream and updates the given Sone, or
-        * creates a new Sone.
+        * Parses a Sone from the given input stream and creates a new Sone from the
+        * parsed data.
         *
-        * @param sone
+        * @param originalSone
         *            The Sone to update
         * @param soneInputStream
         *            The input stream to parse the Sone from
         * @return The parsed Sone
         */
-       public Sone parseSone(Sone sone, InputStream soneInputStream) {
+       public Sone parseSone(Sone originalSone, InputStream soneInputStream) {
                /* TODO - impose a size limit? */
 
                Document document;
@@ -185,9 +209,12 @@ public class SoneDownloader extends AbstractService {
                }
                if (document == null) {
                        /* TODO - mark Sone as bad. */
-                       logger.log(Level.WARNING, "Could not parse XML for Sone %s!", new Object[] { sone });
+                       logger.log(Level.WARNING, "Could not parse XML for Sone %s!", new Object[] { originalSone });
                        return null;
                }
+
+               Sone sone = new Sone(originalSone.getId()).setIdentity(originalSone.getIdentity());
+
                SimpleXML soneXml;
                try {
                        soneXml = SimpleXML.fromDocument(document);
@@ -197,6 +224,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. */
@@ -211,6 +259,17 @@ public class SoneDownloader extends AbstractService {
                        return null;
                }
 
+               SimpleXML clientXml = soneXml.getNode("client");
+               if (clientXml != null) {
+                       String clientName = clientXml.getValue("name", null);
+                       String clientVersion = clientXml.getValue("version", null);
+                       if ((clientName == null) || (clientVersion == null)) {
+                               logger.log(Level.WARNING, "Download Sone %s with client XML but missing name or version!", sone);
+                               return null;
+                       }
+                       sone.setClient(new Client(clientName, clientVersion));
+               }
+
                String soneRequestUri = soneXml.getValue("request-uri", null);
                if (soneRequestUri != null) {
                        try {
@@ -260,6 +319,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)) {
@@ -268,7 +328,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 });
@@ -337,7 +401,7 @@ public class SoneDownloader extends AbstractService {
                        sone.setPosts(posts);
                        sone.setReplies(replies);
                        sone.setLikePostIds(likedPostIds);
-                       sone.setModificationCounter(0);
+                       sone.setLikeReplyIds(likedReplyIds);
                }
 
                return sone;