--- /dev/null
+package net.pterodactylus.sone.core
+
+import com.google.common.eventbus.*
+import com.google.inject.*
+import net.pterodactylus.sone.core.event.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.data.Sone.*
+import net.pterodactylus.sone.database.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.util.logging.*
+import javax.inject.Inject
+
+/**
+ * An `UpdatedSoneProcessor` is called to process a [Sone] after it has been
+ * downloaded from Freenet.
+ */
+@ImplementedBy(DefaultUpdateSoneProcessor::class)
+interface UpdatedSoneProcessor {
+
+ fun updateSone(sone: Sone)
+
+}
+
+abstract class BasicUpdateSoneProcessor(private val database: Database, private val eventBus: EventBus) :
+ UpdatedSoneProcessor {
+
+ private val logger = Logging.getLogger(UpdatedSoneProcessor::javaClass.name)!!
+
+ override fun updateSone(sone: Sone) {
+ val storedSone = database.getSone(sone.id) ?: return
+ if (!soneCanBeUpdated(storedSone, sone)) {
+ logger.fine("Downloaded Sone $sone can not update stored Sone $storedSone.")
+ return
+ }
+
+ SoneComparison(storedSone, sone).apply {
+ newPosts
+ .onEach { post -> if (post.time <= sone.followingTime) post.isKnown = true }
+ .mapNotNull { post -> post.isKnown.ifFalse { NewPostFoundEvent(post) } }
+ .forEach(eventBus::post)
+ removedPosts
+ .map { PostRemovedEvent(it) }
+ .forEach(eventBus::post)
+ newPostReplies
+ .onEach { postReply -> if (postReply.time <= sone.followingTime) postReply.isKnown = true }
+ .mapNotNull { postReply -> postReply.isKnown.ifFalse { NewPostReplyFoundEvent(postReply) } }
+ .forEach(eventBus::post)
+ removedPostReplies
+ .map { PostReplyRemovedEvent(it) }
+ .forEach(eventBus::post)
+ }
+ database.storeSone(sone)
+ sone.options = storedSone.options
+ sone.isKnown = storedSone.isKnown
+ sone.status = if (sone.time != 0L) SoneStatus.idle else SoneStatus.unknown
+ }
+
+ protected abstract fun soneCanBeUpdated(storedSone: Sone, newSone: Sone): Boolean
+
+ private val Sone.followingTime get() = database.getFollowingTime(id) ?: 0
+
+}
+
+class DefaultUpdateSoneProcessor @Inject constructor(database: Database, eventBus: EventBus) :
+ BasicUpdateSoneProcessor(database, eventBus) {
+
+ override fun soneCanBeUpdated(storedSone: Sone, newSone: Sone) =
+ newSone.time > storedSone.time
+
+}