+ /**
+ * 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) {
+ Optional<Sone> storedSone = getSone(sone.getId());
+ if (storedSone.isPresent()) {
+ if (!soneRescueMode && !(sone.getTime() > storedSone.get().getTime())) {
+ logger.log(Level.FINE, String.format("Downloaded Sone %s is not newer than stored Sone %s.", sone, storedSone));
+ return;
+ }
+ /* find removed posts. */
+ Collection<Post> existingPosts = database.getPosts(sone.getId());
+ for (Post oldPost : existingPosts) {
+ if (!sone.getPosts().contains(oldPost)) {
+ eventBus.post(new PostRemovedEvent(oldPost));
+ }
+ }
+ /* find new posts. */
+ for (Post newPost : sone.getPosts()) {
+ if (existingPosts.contains(newPost)) {
+ continue;
+ }
+ if (newPost.getTime() < getSoneFollowingTime(sone)) {
+ newPost.setKnown(true);
+ } else if (!newPost.isKnown()) {
+ eventBus.post(new NewPostFoundEvent(newPost));
+ }
+ }
+ /* store posts. */
+ database.storePosts(sone, sone.getPosts());
+ if (!soneRescueMode) {
+ for (PostReply reply : storedSone.get().getReplies()) {
+ if (!sone.getReplies().contains(reply)) {
+ eventBus.post(new PostReplyRemovedEvent(reply));
+ }
+ }
+ }
+ Set<PostReply> storedReplies = storedSone.get().getReplies();
+ for (PostReply reply : sone.getReplies()) {
+ if (storedReplies.contains(reply)) {
+ continue;
+ }
+ if (reply.getTime() < getSoneFollowingTime(sone)) {
+ reply.modify().setKnown().update(Optional.<ReplyUpdated<PostReply>>absent());
+ } else if (!reply.isKnown()) {
+ eventBus.post(new NewPostReplyFoundEvent(reply));
+ }
+ }
+ database.storePostReplies(sone, sone.getReplies());
+ for (Album album : storedSone.get().getRootAlbum().getAlbums()) {
+ database.removeAlbum(album);
+ for (Image image : album.getImages()) {
+ database.removeImage(image);
+ }
+ }
+ for (Album album : sone.getRootAlbum().getAlbums()) {
+ database.storeAlbum(album);
+ for (Image image : album.getImages()) {
+ database.storeImage(image);
+ }
+ }
+ synchronized (sones) {
+ sone.setOptions(storedSone.get().getOptions());
+ sone.setKnown(storedSone.get().isKnown());
+ sone.setStatus((sone.getTime() == 0) ? SoneStatus.unknown : SoneStatus.idle);
+ if (sone.isLocal()) {
+ soneInserters.get(storedSone.get()).setSone(sone);
+ touchConfiguration();
+ }
+ sones.put(sone.getId(), sone);
+ }
+ }
+ }
+
+ /**
+ * Deletes the given Sone. This will remove the Sone from the {@link
+ * #getLocalSones() local Sones}, stop 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 (sones) {
+ if (!getLocalSones().contains(sone)) {
+ logger.log(Level.WARNING, String.format("Tried to delete non-local Sone: %s", sone));
+ return;
+ }
+ sones.remove(sone.getId());
+ SoneInserter soneInserter = soneInserters.remove(sone);
+ 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 MarkSoneKnownEvent} 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());
+ }
+ eventBus.post(new MarkSoneKnownEvent(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.
+ *
+ * @param sone
+ * The Sone to load and update
+ */
+ public void loadSone(Sone sone) {
+ if (!sone.isLocal()) {
+ logger.log(Level.FINE, String.format("Tried to load non-local Sone: %s", sone));
+ return;
+ }
+ logger.info(String.format("Loading local Sone: %s", sone));
+
+ /* initialize options. */
+ sone.getOptions().addBooleanOption("AutoFollow", new DefaultOption<Boolean>(false));
+ sone.getOptions().addBooleanOption("EnableSoneInsertNotifications", new DefaultOption<Boolean>(false));
+ sone.getOptions().addBooleanOption("ShowNotification/NewSones", new DefaultOption<Boolean>(true));
+ sone.getOptions().addBooleanOption("ShowNotification/NewPosts", new DefaultOption<Boolean>(true));
+ sone.getOptions().addBooleanOption("ShowNotification/NewReplies", new DefaultOption<Boolean>(true));
+ sone.getOptions().addEnumOption("ShowCustomAvatars", new DefaultOption<ShowCustomAvatars>(ShowCustomAvatars.NEVER));
+
+ /* load Sone. */
+ String sonePrefix = "Sone/" + sone.getId();
+ Long soneTime = configuration.getLongValue(sonePrefix + "/Time").getValue(null);
+ if (soneTime == null) {
+ logger.log(Level.INFO, "Could not load Sone because no Sone has been saved.");
+ return;
+ }
+ String lastInsertFingerprint = configuration.getStringValue(sonePrefix + "/LastInsertFingerprint").getValue("");
+
+ /* load profile. */
+ Profile profile = new Profile(sone);
+ String firstName = configuration.getStringValue(sonePrefix + "/Profile/FirstName").getValue(null);
+ String middleName = configuration.getStringValue(sonePrefix + "/Profile/MiddleName").getValue(null);
+ String lastName = configuration.getStringValue(sonePrefix + "/Profile/LastName").getValue(null);
+ profile.modify().setFirstName(firstName).setMiddleName(middleName).setLastName(lastName).update();
+ Integer birthDay = configuration.getIntValue(sonePrefix + "/Profile/BirthDay").getValue(null);
+ Integer birthMonth = configuration.getIntValue(sonePrefix + "/Profile/BirthMonth").getValue(null);
+ Integer birthYear = configuration.getIntValue(sonePrefix + "/Profile/BirthYear").getValue(null);
+ profile.modify().setBirthYear(birthYear).setBirthMonth(birthMonth).setBirthDay(birthDay).update();
+
+ /* load profile fields. */
+ while (true) {
+ String fieldPrefix = sonePrefix + "/Profile/Fields/" + profile.getFields().size();
+ String fieldName = configuration.getStringValue(fieldPrefix + "/Name").getValue(null);
+ if (fieldName == null) {
+ break;
+ }
+ String fieldValue = configuration.getStringValue(fieldPrefix + "/Value").getValue("");
+ profile.addField(fieldName).setValue(fieldValue);
+ }
+
+ /* load posts. */
+ Set<Post> posts = new HashSet<Post>();
+ while (true) {
+ String postPrefix = sonePrefix + "/Posts/" + posts.size();
+ String postId = configuration.getStringValue(postPrefix + "/ID").getValue(null);
+ if (postId == null) {
+ break;
+ }
+ String postRecipientId = configuration.getStringValue(postPrefix + "/Recipient").getValue(null);
+ long postTime = configuration.getLongValue(postPrefix + "/Time").getValue((long) 0);
+ String postText = configuration.getStringValue(postPrefix + "/Text").getValue(null);
+ if ((postTime == 0) || (postText == null)) {
+ logger.log(Level.WARNING, "Invalid post found, aborting load!");
+ return;
+ }
+ PostBuilder postBuilder = sone.newPostBuilder().withId(postId).withTime(postTime).withText(postText);
+ if ((postRecipientId != null) && (postRecipientId.length() == 43)) {
+ postBuilder.to(of(postRecipientId));
+ }
+ posts.add(postBuilder.build(Optional.<PostCreated>absent()));
+ }