Detect if a Sone has been changed back to the previous insert state.
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Fri, 12 Nov 2010 21:20:34 +0000 (22:20 +0100)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Fri, 12 Nov 2010 21:20:34 +0000 (22:20 +0100)
src/main/java/net/pterodactylus/sone/core/Core.java
src/main/java/net/pterodactylus/sone/core/SoneDownloader.java
src/main/java/net/pterodactylus/sone/core/SoneInserter.java
src/main/java/net/pterodactylus/sone/data/Sone.java
src/main/java/net/pterodactylus/sone/template/SoneAccessor.java
src/main/java/net/pterodactylus/sone/web/ajax/GetSoneStatusPage.java

index 668a938..60101b4 100644 (file)
@@ -400,6 +400,18 @@ public class Core implements IdentityListener {
        }
 
        /**
+        * Returns whether the given Sone has been modified.
+        *
+        * @param sone
+        *            The Sone to check for modifications
+        * @return {@code true} if a modification has been detected in the Sone,
+        *         {@code false} otherwise
+        */
+       public boolean isModifiedSone(Sone sone) {
+               return (soneInserters.containsKey(sone)) ? soneInserters.get(sone).isModified() : false;
+       }
+
+       /**
         * Returns the post with the given ID.
         *
         * @param postId
@@ -601,10 +613,6 @@ public class Core implements IdentityListener {
        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;
        }
 
@@ -693,7 +701,6 @@ public class Core implements IdentityListener {
                                storedSone.setLikePostIds(sone.getLikedPostIds());
                                storedSone.setLikeReplyIds(sone.getLikedReplyIds());
                                storedSone.setLatestEdition(sone.getRequestUri().getEdition());
-                               storedSone.setModificationCounter(0);
                        }
                }
        }
@@ -748,7 +755,7 @@ public class Core implements IdentityListener {
                        logger.log(Level.INFO, "Could not load Sone because no Sone has been saved.");
                        return;
                }
-               long soneModificationCounter = configuration.getLongValue(sonePrefix + "/ModificationCounter").getValue((long) 0);
+               String lastInsertFingerprint = configuration.getStringValue(sonePrefix + "/LastInsertFingerprint").getValue("");
 
                /* load profile. */
                Profile profile = new Profile();
@@ -833,7 +840,7 @@ public class Core implements IdentityListener {
                        sone.setLikePostIds(likedPostIds);
                        sone.setLikeReplyIds(likedReplyIds);
                        sone.setFriends(friends);
-                       sone.setModificationCounter(soneModificationCounter);
+                       soneInserters.get(sone).setLastInsertFingerprint(lastInsertFingerprint);
                }
                synchronized (newSones) {
                        for (String friend : friends) {
@@ -875,7 +882,7 @@ public class Core implements IdentityListener {
                        /* save Sone into configuration. */
                        String sonePrefix = "Sone/" + sone.getId();
                        configuration.getLongValue(sonePrefix + "/Time").setValue(sone.getTime());
-                       configuration.getLongValue(sonePrefix + "/ModificationCounter").setValue(sone.getModificationCounter());
+                       configuration.getStringValue(sonePrefix + "/LastInsertFingerprint").setValue(soneInserters.get(sone).getLastInsertFingerprint());
 
                        /* save profile. */
                        Profile profile = sone.getProfile();
index c5f4816..7133797 100644 (file)
@@ -341,7 +341,6 @@ public class SoneDownloader extends AbstractService {
                        sone.setPosts(posts);
                        sone.setReplies(replies);
                        sone.setLikePostIds(likedPostIds);
-                       sone.setModificationCounter(0);
                }
 
                return sone;
index 5b281f6..86ea92a 100644 (file)
@@ -76,6 +76,12 @@ public class SoneInserter extends AbstractService {
        /** The Sone to insert. */
        private final Sone sone;
 
+       /** Whether a modification has been detected. */
+       private volatile boolean modified = false;
+
+       /** The fingerprint of the last insert. */
+       private volatile String lastInsertFingerprint;
+
        /**
         * Creates a new Sone inserter.
         *
@@ -108,6 +114,36 @@ public class SoneInserter extends AbstractService {
                SoneInserter.insertionDelay = insertionDelay;
        }
 
+       /**
+        * Returns the fingerprint of the last insert.
+        *
+        * @return The fingerprint of the last insert
+        */
+       public String getLastInsertFingerprint() {
+               return lastInsertFingerprint;
+       }
+
+       /**
+        * Sets the fingerprint of the last insert.
+        *
+        * @param lastInsertFingerprint
+        *            The fingerprint of the last insert
+        */
+       public void setLastInsertFingerprint(String lastInsertFingerprint) {
+               this.lastInsertFingerprint = lastInsertFingerprint;
+       }
+
+       /**
+        * Returns whether the Sone inserter has detected a modification of the
+        * Sone.
+        *
+        * @return {@code true} if the Sone has been modified, {@code false}
+        *         otherwise
+        */
+       public boolean isModified() {
+               return modified;
+       }
+
        //
        // SERVICE METHODS
        //
@@ -117,21 +153,30 @@ public class SoneInserter extends AbstractService {
         */
        @Override
        protected void serviceRun() {
-               long modificationCounter = 0;
                long lastModificationTime = 0;
+               String lastFingerprint = "";
                while (!shouldStop()) {
                        /* check every seconds. */
                        sleep(1000);
 
                        InsertInformation insertInformation = null;
                        synchronized (sone) {
-                               if (sone.getModificationCounter() > modificationCounter) {
-                                       modificationCounter = sone.getModificationCounter();
-                                       lastModificationTime = System.currentTimeMillis();
-                                       sone.setTime(lastModificationTime);
-                                       logger.log(Level.FINE, "Sone %s has been modified, waiting %d seconds before inserting.", new Object[] { sone.getName(), insertionDelay });
+                               String fingerprint = sone.getFingerprint();
+                               if (!fingerprint.equals(lastFingerprint)) {
+                                       if (fingerprint.equals(lastInsertFingerprint)) {
+                                               modified = false;
+                                               lastModificationTime = 0;
+                                               logger.log(Level.FINE, "Sone %s has been reverted to last insert state.", sone);
+                                       } else {
+                                               lastModificationTime = System.currentTimeMillis();
+                                               modified = true;
+                                               sone.setTime(lastModificationTime);
+                                               logger.log(Level.FINE, "Sone %s has been modified, waiting %d seconds before inserting.", new Object[] { sone.getName(), insertionDelay });
+                                       }
+                                       lastFingerprint = fingerprint;
                                }
-                               if ((lastModificationTime > 0) && ((System.currentTimeMillis() - lastModificationTime) > (insertionDelay * 1000))) {
+                               if (modified && (lastModificationTime > 0) && ((System.currentTimeMillis() - lastModificationTime) > (insertionDelay * 1000))) {
+                                       lastInsertFingerprint = fingerprint;
                                        insertInformation = new InsertInformation(sone);
                                }
                        }
@@ -163,12 +208,11 @@ public class SoneInserter extends AbstractService {
                                 */
                                if (success) {
                                        synchronized (sone) {
-                                               if (sone.getModificationCounter() == modificationCounter) {
+                                               if (lastInsertFingerprint.equals(sone.getFingerprint())) {
                                                        logger.log(Level.FINE, "Sone “%s” was not modified further, resetting counter…", new Object[] { sone });
-                                                       sone.setModificationCounter(0);
                                                        core.saveSone(sone);
-                                                       modificationCounter = 0;
                                                        lastModificationTime = 0;
+                                                       modified = false;
                                                }
                                        }
                                }
index b14c28f..7fd5fa6 100644 (file)
@@ -96,9 +96,6 @@ public class Sone {
        /** The IDs of all liked replies. */
        private final Set<String> likedReplyIds = Collections.synchronizedSet(new HashSet<String>());
 
-       /** Modification count. */
-       private volatile long modificationCounter = 0;
-
        /**
         * Creates a new Sone.
         *
@@ -267,7 +264,7 @@ public class Sone {
         *
         * @return A copy of the profile
         */
-       public Profile getProfile() {
+       public synchronized Profile getProfile() {
                return new Profile(profile);
        }
 
@@ -281,7 +278,6 @@ public class Sone {
         */
        public synchronized void setProfile(Profile profile) {
                this.profile = new Profile(profile);
-               modificationCounter++;
        }
 
        /**
@@ -369,7 +365,6 @@ public class Sone {
        public synchronized Sone setPosts(Collection<Post> posts) {
                this.posts.clear();
                this.posts.addAll(posts);
-               modificationCounter++;
                return this;
        }
 
@@ -383,7 +378,6 @@ public class Sone {
        public synchronized void addPost(Post post) {
                if (post.getSone().equals(this) && posts.add(post)) {
                        logger.log(Level.FINEST, "Adding %s to “%s”.", new Object[] { post, getName() });
-                       modificationCounter++;
                }
        }
 
@@ -394,8 +388,8 @@ public class Sone {
         *            The post to remove
         */
        public synchronized void removePost(Post post) {
-               if (post.getSone().equals(this) && posts.remove(post)) {
-                       modificationCounter++;
+               if (post.getSone().equals(this)) {
+                       posts.remove(post);
                }
        }
 
@@ -418,7 +412,6 @@ public class Sone {
        public synchronized Sone setReplies(Collection<Reply> replies) {
                this.replies.clear();
                this.replies.addAll(replies);
-               modificationCounter++;
                return this;
        }
 
@@ -430,8 +423,8 @@ public class Sone {
         *            The reply to add
         */
        public synchronized void addReply(Reply reply) {
-               if (reply.getSone().equals(this) && replies.add(reply)) {
-                       modificationCounter++;
+               if (reply.getSone().equals(this)) {
+                       replies.add(reply);
                }
        }
 
@@ -442,8 +435,8 @@ public class Sone {
         *            The reply to remove
         */
        public synchronized void removeReply(Reply reply) {
-               if (reply.getSone().equals(this) && replies.remove(reply)) {
-                       modificationCounter++;
+               if (reply.getSone().equals(this)) {
+                       replies.remove(reply);
                }
        }
 
@@ -466,7 +459,6 @@ public class Sone {
        public synchronized Sone setLikePostIds(Set<String> likedPostIds) {
                this.likedPostIds.clear();
                this.likedPostIds.addAll(likedPostIds);
-               modificationCounter++;
                return this;
        }
 
@@ -490,9 +482,7 @@ public class Sone {
         * @return This Sone (for method chaining)
         */
        public synchronized Sone addLikedPostId(String postId) {
-               if (likedPostIds.add(postId)) {
-                       modificationCounter++;
-               }
+               likedPostIds.add(postId);
                return this;
        }
 
@@ -504,9 +494,7 @@ public class Sone {
         * @return This Sone (for method chaining)
         */
        public synchronized Sone removeLikedPostId(String postId) {
-               if (likedPostIds.remove(postId)) {
-                       modificationCounter++;
-               }
+               likedPostIds.remove(postId);
                return this;
        }
 
@@ -529,7 +517,6 @@ public class Sone {
        public synchronized Sone setLikeReplyIds(Set<String> likedReplyIds) {
                this.likedReplyIds.clear();
                this.likedReplyIds.addAll(likedReplyIds);
-               modificationCounter++;
                return this;
        }
 
@@ -553,9 +540,7 @@ public class Sone {
         * @return This Sone (for method chaining)
         */
        public synchronized Sone addLikedReplyId(String replyId) {
-               if (likedReplyIds.add(replyId)) {
-                       modificationCounter++;
-               }
+               likedReplyIds.add(replyId);
                return this;
        }
 
@@ -567,29 +552,72 @@ public class Sone {
         * @return This Sone (for method chaining)
         */
        public synchronized Sone removeLikedReplyId(String replyId) {
-               if (likedReplyIds.remove(replyId)) {
-                       modificationCounter++;
-               }
+               likedReplyIds.remove(replyId);
                return this;
        }
 
        /**
-        * Returns the modification counter.
+        * Returns a fingerprint of this Sone. The fingerprint only depends on data
+        * that is actually stored when a Sone is inserted. The fingerprint can be
+        * used to detect changes in Sone data and can also be used to detect if
+        * previous changes are reverted.
         *
-        * @return The modification counter
+        * @return The fingerprint of this Sone
         */
-       public synchronized long getModificationCounter() {
-               return modificationCounter;
-       }
+       public synchronized String getFingerprint() {
+               StringBuilder fingerprint = new StringBuilder();
+               fingerprint.append("Profile(");
+               if (profile.getFirstName() != null) {
+                       fingerprint.append("FirstName(").append(profile.getFirstName()).append(')');
+               }
+               if (profile.getMiddleName() != null) {
+                       fingerprint.append("MiddleName(").append(profile.getMiddleName()).append(')');
+               }
+               if (profile.getLastName() != null) {
+                       fingerprint.append("LastName(").append(profile.getLastName()).append(')');
+               }
+               if (profile.getBirthDay() != null) {
+                       fingerprint.append("BirthDay(").append(profile.getBirthDay()).append(')');
+               }
+               if (profile.getBirthMonth() != null) {
+                       fingerprint.append("BirthMonth(").append(profile.getBirthMonth()).append(')');
+               }
+               if (profile.getBirthYear() != null) {
+                       fingerprint.append("BirthYear(").append(profile.getBirthYear()).append(')');
+               }
+               fingerprint.append(")");
 
-       /**
-        * Sets the modification counter.
-        *
-        * @param modificationCounter
-        *            The new modification counter
-        */
-       public synchronized void setModificationCounter(long modificationCounter) {
-               this.modificationCounter = modificationCounter;
+               fingerprint.append("Posts(");
+               for (Post post : getPosts()) {
+                       fingerprint.append("Post(").append(post.getId()).append(')');
+               }
+               fingerprint.append(")");
+
+               List<Reply> replies = new ArrayList<Reply>(getReplies());
+               Collections.sort(replies, Reply.TIME_COMPARATOR);
+               fingerprint.append("Replies(");
+               for (Reply reply : replies) {
+                       fingerprint.append("Reply(").append(reply.getId()).append(')');
+               }
+               fingerprint.append(')');
+
+               List<String> likedPostIds = new ArrayList<String>(getLikedPostIds());
+               Collections.sort(likedPostIds);
+               fingerprint.append("LikedPosts(");
+               for (String likedPostId : likedPostIds) {
+                       fingerprint.append("Post(").append(likedPostId).append(')');
+               }
+               fingerprint.append(')');
+
+               List<String> likedReplyIds = new ArrayList<String>(getLikedReplyIds());
+               Collections.sort(likedReplyIds);
+               fingerprint.append("LikedReplies(");
+               for (String likedReplyId : likedReplyIds) {
+                       fingerprint.append("Reply(").append(likedReplyId).append(')');
+               }
+               fingerprint.append(')');
+
+               return fingerprint.toString();
        }
 
        //
index a9ee78b..eac6e2c 100644 (file)
@@ -74,7 +74,7 @@ public class SoneAccessor extends ReflectionAccessor {
                        Sone currentSone = (Sone) dataProvider.getData("currentSone");
                        return (currentSone != null) && currentSone.equals(sone);
                } else if (member.equals("modified")) {
-                       return sone.getModificationCounter() > 0;
+                       return core.isModifiedSone(sone);
                } else if (member.equals("status")) {
                        return core.getSoneStatus(sone);
                } else if (member.equals("unknown")) {
index cac308e..ff8e181 100644 (file)
@@ -20,6 +20,7 @@ package net.pterodactylus.sone.web.ajax;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 
+import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.core.Core.SoneStatus;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.template.SoneAccessor;
@@ -28,7 +29,7 @@ import net.pterodactylus.util.json.JsonObject;
 
 /**
  * AJAX page that reeturns the status of a sone, as a) a {@link SoneStatus} name
- * and b) a “modified” boolean (as per {@link Sone#getModificationCounter()}).
+ * and b) a “modified” boolean (as per {@link Core#isModifiedSone(Sone)}).
  *
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
@@ -56,7 +57,7 @@ public class GetSoneStatusPage extends JsonPage {
                String soneId = request.getHttpRequest().getParam("sone");
                Sone sone = webInterface.getCore().getSone(soneId);
                SoneStatus soneStatus = webInterface.getCore().getSoneStatus(sone);
-               return new JsonObject().put("status", soneStatus.name()).put("name", SoneAccessor.getNiceName(sone)).put("modified", sone.getModificationCounter() > 0).put("lastUpdated", new SimpleDateFormat("MMM d, yyyy, HH:mm:ss").format(new Date(sone.getTime()))).put("age", (System.currentTimeMillis() - sone.getTime()) / 1000);
+               return new JsonObject().put("status", soneStatus.name()).put("name", SoneAccessor.getNiceName(sone)).put("modified", webInterface.getCore().isModifiedSone(sone)).put("lastUpdated", new SimpleDateFormat("MMM d, yyyy, HH:mm:ss").format(new Date(sone.getTime()))).put("age", (System.currentTimeMillis() - sone.getTime()) / 1000);
        }
 
        /**