+ * Lets the given local Sone unfollow the Sone with the given ID.
+ *
+ * @param sone
+ * The local Sone that should unfollow another Sone
+ * @param soneId
+ * The ID of the Sone being unfollowed
+ */
+ public void unfollowSone(Sone sone, String soneId) {
+ Validation.begin().isNotNull("Sone", sone).isNotNull("Sone ID", soneId).check();
+ unfollowSone(sone, getSone(soneId, false));
+ }
+
+ /**
+ * Lets the given local Sone unfollow the other given Sone. If the given
+ * local Sone is the last local Sone that followed the given Sone, its
+ * following time will be removed.
+ *
+ * @param sone
+ * The local Sone that should unfollow another Sone
+ * @param unfollowedSone
+ * The Sone being unfollowed
+ */
+ public void unfollowSone(Sone sone, Sone unfollowedSone) {
+ Validation.begin().isNotNull("Sone", sone).isNotNull("Unfollowed Sone", unfollowedSone).check();
+ sone.removeFriend(unfollowedSone.getId());
+ boolean unfollowedSoneStillFollowed = false;
+ for (Sone localSone : getLocalSones()) {
+ unfollowedSoneStillFollowed |= localSone.hasFriend(unfollowedSone.getId());
+ }
+ if (!unfollowedSoneStillFollowed) {
+ synchronized (soneFollowingTimes) {
+ soneFollowingTimes.remove(unfollowedSone);
+ }
+ }
+ touchConfiguration();
+ }
+
+ /**
+ * Retrieves the trust relationship from the origin to the target. If the
+ * trust relationship can not be retrieved, {@code null} is returned.
+ *
+ * @see Identity#getTrust(OwnIdentity)
+ * @param origin
+ * The origin of the trust tree
+ * @param target
+ * The target of the trust
+ * @return The trust relationship
+ */
+ public Trust getTrust(Sone origin, Sone target) {
+ if (!isLocalSone(origin)) {
+ logger.log(Level.WARNING, String.format("Tried to get trust from remote Sone: %s", origin));
+ return null;
+ }
+ webOfTrustUpdater.getTrust((OwnIdentity) origin.getIdentity(), target.getIdentity());
+ return target.getIdentity().getTrust((OwnIdentity) origin.getIdentity());
+ }
+
+ /**
+ * Sets the trust value of the given origin Sone for the target Sone.
+ *
+ * @param origin
+ * The origin Sone
+ * @param target
+ * The target Sone
+ * @param trustValue
+ * The trust value (from {@code -100} to {@code 100})
+ */
+ public void setTrust(Sone origin, Sone target, int trustValue) {
+ Validation.begin().isNotNull("Trust Origin", origin).check().isInstanceOf("Trust Origin", origin.getIdentity(), OwnIdentity.class).isNotNull("Trust Target", target).isLessOrEqual("Trust Value", trustValue, 100).isGreaterOrEqual("Trust Value", trustValue, -100).check();
+ webOfTrustUpdater.setTrust((OwnIdentity) origin.getIdentity(), target.getIdentity(), trustValue, preferences.getTrustComment());
+ }
+
+ /**
+ * Removes any trust assignment for the given target Sone.
+ *
+ * @param origin
+ * The trust origin
+ * @param target
+ * The trust target
+ */
+ public void removeTrust(Sone origin, Sone target) {
+ Validation.begin().isNotNull("Trust Origin", origin).isNotNull("Trust Target", target).check().isInstanceOf("Trust Origin Identity", origin.getIdentity(), OwnIdentity.class).check();
+ webOfTrustUpdater.setTrust((OwnIdentity) origin.getIdentity(), target.getIdentity(), null, null);
+ }
+
+ /**
+ * Assigns the configured positive trust value for the given target.
+ *
+ * @param origin
+ * The trust origin
+ * @param target
+ * The trust target
+ */
+ public void trustSone(Sone origin, Sone target) {
+ setTrust(origin, target, preferences.getPositiveTrust());
+ }
+
+ /**
+ * Assigns the configured negative trust value for the given target.
+ *
+ * @param origin
+ * The trust origin
+ * @param target
+ * The trust target
+ */
+ public void distrustSone(Sone origin, Sone target) {
+ setTrust(origin, target, preferences.getNegativeTrust());
+ }
+
+ /**
+ * Removes the trust assignment for the given target.
+ *
+ * @param origin
+ * The trust origin
+ * @param target
+ * The trust target
+ */
+ public void untrustSone(Sone origin, Sone target) {
+ removeTrust(origin, target);
+ }
+
+ /**
+ * Updates the stored Sone with the given Sone.
+ *
+ * @param sone
+ * The updated Sone
+ */
+ public void updateSone(Sone sone) {
+ updateSone(sone, false);
+ }
+
+ /**
+ * Updates the stored Sone with the given Sone. If {@code soneRescueMode} is
+ * {@code true}, an older Sone than the current Sone can be given to restore
+ * an old state.
+ *
+ * @param sone
+ * The Sone to update
+ * @param soneRescueMode
+ * {@code true} if the stored Sone should be updated regardless
+ * of the age of the given Sone
+ */
+ public void updateSone(Sone sone, boolean soneRescueMode) {
+ if (hasSone(sone.getId())) {
+ Sone storedSone = getSone(sone.getId());
+ if (!soneRescueMode && !(sone.getTime() > storedSone.getTime())) {
+ logger.log(Level.FINE, String.format("Downloaded Sone %s is not newer than stored Sone %s.", sone, storedSone));
+ return;
+ }
+ synchronized (posts) {
+ if (!soneRescueMode) {
+ for (Post post : storedSone.getPosts()) {
+ posts.remove(post.getId());
+ if (!sone.getPosts().contains(post)) {
+ coreListenerManager.firePostRemoved(post);
+ }
+ }
+ }
+ List<Post> storedPosts = storedSone.getPosts();
+ synchronized (knownPosts) {
+ for (Post post : sone.getPosts()) {
+ post.setSone(storedSone).setKnown(knownPosts.contains(post.getId()));
+ if (!storedPosts.contains(post)) {
+ if (post.getTime() < getSoneFollowingTime(sone)) {
+ knownPosts.add(post.getId());
+ } else if (!knownPosts.contains(post.getId())) {
+ sone.setKnown(false);
+ coreListenerManager.fireNewPostFound(post);
+ }
+ }
+ posts.put(post.getId(), post);
+ }
+ }
+ }
+ synchronized (replies) {
+ if (!soneRescueMode) {
+ for (PostReply reply : storedSone.getReplies()) {
+ replies.remove(reply.getId());
+ if (!sone.getReplies().contains(reply)) {
+ coreListenerManager.fireReplyRemoved(reply);
+ }
+ }
+ }
+ Set<PostReply> storedReplies = storedSone.getReplies();
+ synchronized (knownReplies) {
+ for (PostReply reply : sone.getReplies()) {
+ reply.setSone(storedSone).setKnown(knownReplies.contains(reply.getId()));
+ if (!storedReplies.contains(reply)) {
+ if (reply.getTime() < getSoneFollowingTime(sone)) {
+ knownReplies.add(reply.getId());
+ } else if (!knownReplies.contains(reply.getId())) {
+ reply.setKnown(false);
+ coreListenerManager.fireNewReplyFound(reply);
+ }
+ }
+ replies.put(reply.getId(), reply);
+ }
+ }
+ }
+ synchronized (albums) {
+ synchronized (images) {
+ for (Album album : storedSone.getAlbums()) {
+ albums.remove(album.getId());
+ for (Image image : album.getImages()) {
+ images.remove(image.getId());
+ }
+ }
+ for (Album album : sone.getAlbums()) {
+ albums.put(album.getId(), album);
+ for (Image image : album.getImages()) {
+ images.put(image.getId(), image);
+ }
+ }
+ }
+ }
+ synchronized (storedSone) {
+ if (!soneRescueMode || (sone.getTime() > storedSone.getTime())) {
+ storedSone.setTime(sone.getTime());
+ }
+ storedSone.setClient(sone.getClient());
+ storedSone.setProfile(sone.getProfile());
+ if (soneRescueMode) {
+ for (Post post : sone.getPosts()) {
+ storedSone.addPost(post);
+ }
+ for (PostReply reply : sone.getReplies()) {
+ storedSone.addReply(reply);
+ }
+ for (String likedPostId : sone.getLikedPostIds()) {
+ storedSone.addLikedPostId(likedPostId);
+ }
+ for (String likedReplyId : sone.getLikedReplyIds()) {
+ storedSone.addLikedReplyId(likedReplyId);
+ }
+ for (Album album : sone.getAlbums()) {
+ storedSone.addAlbum(album);
+ }
+ } else {
+ storedSone.setPosts(sone.getPosts());
+ storedSone.setReplies(sone.getReplies());
+ storedSone.setLikePostIds(sone.getLikedPostIds());
+ storedSone.setLikeReplyIds(sone.getLikedReplyIds());
+ storedSone.setAlbums(sone.getAlbums());
+ }
+ storedSone.setLatestEdition(sone.getLatestEdition());
+ }
+ }
+ }
+
+ /**
+ * Deletes the given Sone. This will remove the Sone from the
+ * {@link #getLocalSone(String) local Sones}, stops its {@link SoneInserter}
+ * and remove the context from its identity.
+ *
+ * @param sone
+ * The Sone to delete
+ */
+ public void deleteSone(Sone sone) {
+ if (!(sone.getIdentity() instanceof OwnIdentity)) {
+ logger.log(Level.WARNING, String.format("Tried to delete Sone of non-own identity: %s", sone));
+ return;
+ }
+ synchronized (localSones) {
+ if (!localSones.containsKey(sone.getId())) {
+ logger.log(Level.WARNING, String.format("Tried to delete non-local Sone: %s", sone));
+ return;
+ }
+ localSones.remove(sone.getId());
+ SoneInserter soneInserter = soneInserters.remove(sone);
+ soneInserter.removeSoneInsertListener(this);
+ soneInserter.stop();
+ }
+ webOfTrustUpdater.removeContext((OwnIdentity) sone.getIdentity(), "Sone");
+ webOfTrustUpdater.removeProperty((OwnIdentity) sone.getIdentity(), "Sone.LatestEdition");
+ try {
+ configuration.getLongValue("Sone/" + sone.getId() + "/Time").setValue(null);
+ } catch (ConfigurationException ce1) {
+ logger.log(Level.WARNING, "Could not remove Sone from configuration!", ce1);
+ }
+ }
+
+ /**
+ * Marks the given Sone as known. If the Sone was not {@link Post#isKnown()
+ * known} before, a {@link CoreListener#markSoneKnown(Sone)} event is fired.
+ *
+ * @param sone
+ * The Sone to mark as known
+ */
+ public void markSoneKnown(Sone sone) {
+ if (!sone.isKnown()) {
+ sone.setKnown(true);
+ synchronized (knownSones) {
+ knownSones.add(sone.getId());
+ }
+ coreListenerManager.fireMarkSoneKnown(sone);
+ touchConfiguration();
+ }
+ }
+
+ /**
+ * Loads and updates the given Sone from the configuration. If any error is
+ * encountered, loading is aborted and the given Sone is not changed.