}
/**
+ * 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
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;
}
storedSone.setLikePostIds(sone.getLikedPostIds());
storedSone.setLikeReplyIds(sone.getLikedReplyIds());
storedSone.setLatestEdition(sone.getRequestUri().getEdition());
- storedSone.setModificationCounter(0);
}
}
}
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();
sone.setLikePostIds(likedPostIds);
sone.setLikeReplyIds(likedReplyIds);
sone.setFriends(friends);
- sone.setModificationCounter(soneModificationCounter);
+ soneInserters.get(sone).setLastInsertFingerprint(lastInsertFingerprint);
}
synchronized (newSones) {
for (String friend : friends) {
/* 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();
sone.setPosts(posts);
sone.setReplies(replies);
sone.setLikePostIds(likedPostIds);
- sone.setModificationCounter(0);
}
return sone;
/** 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.
*
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
//
*/
@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);
}
}
*/
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;
}
}
}
/** 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.
*
*
* @return A copy of the profile
*/
- public Profile getProfile() {
+ public synchronized Profile getProfile() {
return new Profile(profile);
}
*/
public synchronized void setProfile(Profile profile) {
this.profile = new Profile(profile);
- modificationCounter++;
}
/**
public synchronized Sone setPosts(Collection<Post> posts) {
this.posts.clear();
this.posts.addAll(posts);
- modificationCounter++;
return this;
}
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++;
}
}
* 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);
}
}
public synchronized Sone setReplies(Collection<Reply> replies) {
this.replies.clear();
this.replies.addAll(replies);
- modificationCounter++;
return this;
}
* 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);
}
}
* 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);
}
}
public synchronized Sone setLikePostIds(Set<String> likedPostIds) {
this.likedPostIds.clear();
this.likedPostIds.addAll(likedPostIds);
- modificationCounter++;
return this;
}
* @return This Sone (for method chaining)
*/
public synchronized Sone addLikedPostId(String postId) {
- if (likedPostIds.add(postId)) {
- modificationCounter++;
- }
+ likedPostIds.add(postId);
return this;
}
* @return This Sone (for method chaining)
*/
public synchronized Sone removeLikedPostId(String postId) {
- if (likedPostIds.remove(postId)) {
- modificationCounter++;
- }
+ likedPostIds.remove(postId);
return this;
}
public synchronized Sone setLikeReplyIds(Set<String> likedReplyIds) {
this.likedReplyIds.clear();
this.likedReplyIds.addAll(likedReplyIds);
- modificationCounter++;
return this;
}
* @return This Sone (for method chaining)
*/
public synchronized Sone addLikedReplyId(String replyId) {
- if (likedReplyIds.add(replyId)) {
- modificationCounter++;
- }
+ likedReplyIds.add(replyId);
return this;
}
* @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();
}
//
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")) {
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;
/**
* 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>
*/
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);
}
/**