/*
- * Sone - Core.java - Copyright © 2010–2013 David Roden
+ * Sone - Core.java - Copyright © 2010–2019 David Roden
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
import static com.google.common.primitives.Longs.tryParse;
import static java.lang.String.format;
import static java.util.logging.Level.WARNING;
+import static java.util.logging.Logger.getLogger;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.*;
import java.util.logging.Level;
import java.util.logging.Logger;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.codahale.metrics.*;
import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidAlbumFound;
import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidImageFound;
import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidParentAlbumFound;
import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidPostFound;
import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidPostReplyFound;
-import net.pterodactylus.sone.core.Options.DefaultOption;
-import net.pterodactylus.sone.core.SoneChangeDetector.PostProcessor;
-import net.pterodactylus.sone.core.SoneChangeDetector.PostReplyProcessor;
-import net.pterodactylus.sone.core.event.ImageInsertFinishedEvent;
-import net.pterodactylus.sone.core.event.MarkPostKnownEvent;
-import net.pterodactylus.sone.core.event.MarkPostReplyKnownEvent;
-import net.pterodactylus.sone.core.event.MarkSoneKnownEvent;
-import net.pterodactylus.sone.core.event.NewPostFoundEvent;
-import net.pterodactylus.sone.core.event.NewPostReplyFoundEvent;
-import net.pterodactylus.sone.core.event.NewSoneFoundEvent;
-import net.pterodactylus.sone.core.event.PostRemovedEvent;
-import net.pterodactylus.sone.core.event.PostReplyRemovedEvent;
-import net.pterodactylus.sone.core.event.SoneLockedEvent;
-import net.pterodactylus.sone.core.event.SoneRemovedEvent;
-import net.pterodactylus.sone.core.event.SoneUnlockedEvent;
+import net.pterodactylus.sone.core.event.*;
import net.pterodactylus.sone.data.Album;
import net.pterodactylus.sone.data.Client;
import net.pterodactylus.sone.data.Image;
import net.pterodactylus.sone.data.Profile.Field;
import net.pterodactylus.sone.data.Reply;
import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.sone.data.Sone.ShowCustomAvatars;
import net.pterodactylus.sone.data.Sone.SoneStatus;
+import net.pterodactylus.sone.data.SoneOptions.LoadExternalContent;
import net.pterodactylus.sone.data.TemporaryImage;
import net.pterodactylus.sone.database.AlbumBuilder;
import net.pterodactylus.sone.database.Database;
import net.pterodactylus.sone.database.PostReplyProvider;
import net.pterodactylus.sone.database.SoneBuilder;
import net.pterodactylus.sone.database.SoneProvider;
-import net.pterodactylus.sone.fcp.FcpInterface;
import net.pterodactylus.sone.freenet.wot.Identity;
import net.pterodactylus.sone.freenet.wot.IdentityManager;
import net.pterodactylus.sone.freenet.wot.OwnIdentity;
import net.pterodactylus.sone.freenet.wot.event.OwnIdentityAddedEvent;
import net.pterodactylus.sone.freenet.wot.event.OwnIdentityRemovedEvent;
import net.pterodactylus.sone.main.SonePlugin;
-import net.pterodactylus.sone.utils.IntegerRangePredicate;
import net.pterodactylus.util.config.Configuration;
import net.pterodactylus.util.config.ConfigurationException;
-import net.pterodactylus.util.logging.Logging;
-import net.pterodactylus.util.number.Numbers;
import net.pterodactylus.util.service.AbstractService;
import net.pterodactylus.util.thread.NamedThreadFactory;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Optional;
-import com.google.common.base.Predicates;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.eventbus.Subscribe;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import kotlin.jvm.functions.Function1;
/**
* The Sone core.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
@Singleton
public class Core extends AbstractService implements SoneProvider, PostProvider, PostReplyProvider {
/** The logger. */
- private static final Logger logger = Logging.getLogger(Core.class);
+ private static final Logger logger = getLogger(Core.class.getName());
/** The start time. */
private final long startupTime = System.currentTimeMillis();
- /** The options. */
- private final Options options = new Options();
+ private final AtomicBoolean debug = new AtomicBoolean(false);
/** The preferences. */
private final Preferences preferences;
/** The trust updater. */
private final WebOfTrustUpdater webOfTrustUpdater;
- /** The FCP interface. */
- private volatile FcpInterface fcpInterface;
-
- /** The times Sones were followed. */
- private final Map<String, Long> soneFollowingTimes = new HashMap<String, Long>();
-
/** Locked local Sones. */
/* synchronize on itself. */
- private final Set<Sone> lockedSones = new HashSet<Sone>();
+ private final Set<Sone> lockedSones = new HashSet<>();
/** Sone inserters. */
/* synchronize access on this on sones. */
- private final Map<Sone, SoneInserter> soneInserters = new HashMap<Sone, SoneInserter>();
+ private final Map<Sone, SoneInserter> soneInserters = new HashMap<>();
/** Sone rescuers. */
/* synchronize access on this on sones. */
- private final Map<Sone, SoneRescuer> soneRescuers = new HashMap<Sone, SoneRescuer>();
+ private final Map<Sone, SoneRescuer> soneRescuers = new HashMap<>();
/** All known Sones. */
- private final Set<String> knownSones = new HashSet<String>();
+ private final Set<String> knownSones = new HashSet<>();
/** The post database. */
private final Database database;
- /** All bookmarked posts. */
- /* synchronize access on itself. */
- private final Set<String> bookmarkedPosts = new HashSet<String>();
-
/** Trusted identities, sorted by own identities. */
private final Multimap<OwnIdentity, Identity> trustedIdentities = Multimaps.synchronizedSetMultimap(HashMultimap.<OwnIdentity, Identity>create());
/** All temporary images. */
- private final Map<String, TemporaryImage> temporaryImages = new HashMap<String, TemporaryImage>();
+ private final Map<String, TemporaryImage> temporaryImages = new HashMap<>();
/** Ticker for threads that mark own elements as known. */
private final ScheduledExecutorService localElementTicker = Executors.newScheduledThreadPool(1);
/** The time the configuration was last touched. */
private volatile long lastConfigurationUpdate;
+ private final MetricRegistry metricRegistry;
+
/**
* Creates a new core.
*
* The database
*/
@Inject
- public Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager, WebOfTrustUpdater webOfTrustUpdater, EventBus eventBus, Database database) {
- super("Sone Core");
- this.configuration = configuration;
- this.freenetInterface = freenetInterface;
- this.identityManager = identityManager;
- this.soneDownloader = new SoneDownloaderImpl(this, freenetInterface);
- this.imageInserter = new ImageInserter(freenetInterface, freenetInterface.new InsertTokenSupplier());
- this.updateChecker = new UpdateChecker(eventBus, freenetInterface);
- this.webOfTrustUpdater = webOfTrustUpdater;
- this.eventBus = eventBus;
- this.database = database;
- preferences = new Preferences(this.eventBus, options);
- }
-
- @VisibleForTesting
- protected Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager, SoneDownloader soneDownloader, ImageInserter imageInserter, UpdateChecker updateChecker, WebOfTrustUpdater webOfTrustUpdater, EventBus eventBus, Database database) {
+ public Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager, SoneDownloader soneDownloader, ImageInserter imageInserter, UpdateChecker updateChecker, WebOfTrustUpdater webOfTrustUpdater, EventBus eventBus, Database database, MetricRegistry metricRegistry) {
super("Sone Core");
this.configuration = configuration;
this.freenetInterface = freenetInterface;
this.webOfTrustUpdater = webOfTrustUpdater;
this.eventBus = eventBus;
this.database = database;
- preferences = new Preferences(this.eventBus, options);
+ this.metricRegistry = metricRegistry;
+ preferences = new Preferences(eventBus);
}
//
return startupTime;
}
+ @Nonnull
+ public boolean getDebug() {
+ return debug.get();
+ }
+
+ public void setDebug() {
+ debug.set(true);
+ eventBus.post(new DebugActivatedEvent());
+ }
+
/**
* Returns the options used by the core.
*
}
/**
- * Sets the FCP interface to use.
- *
- * @param fcpInterface
- * The FCP interface to use
- */
- public void setFcpInterface(FcpInterface fcpInterface) {
- this.fcpInterface = fcpInterface;
- }
-
- /**
* Returns the Sone rescuer for the given local Sone.
*
* @param sone
/**
* {@inheritDocs}
*/
+ @Nonnull
@Override
public Collection<Sone> getSones() {
return database.getSones();
}
+ @Nonnull
+ @Override
+ public Function1<String, Sone> getSoneLoader() {
+ return database.getSoneLoader();
+ }
+
/**
* Returns the Sone with the given ID, regardless whether it’s local or
* remote.
* Sone
*/
@Override
- public Optional<Sone> getSone(String id) {
+ @Nullable
+ public Sone getSone(@Nonnull String id) {
return database.getSone(id);
}
* @return The Sone with the given ID, or {@code null}
*/
public Sone getLocalSone(String id) {
- Optional<Sone> sone = database.getSone(id);
- if (sone.isPresent() && sone.get().isLocal()) {
- return sone.get();
+ Sone sone = database.getSone(id);
+ if ((sone != null) && sone.isLocal()) {
+ return sone;
}
return null;
}
* @return The Sone with the given ID
*/
public Sone getRemoteSone(String id) {
- return database.getSone(id).orNull();
+ return database.getSone(id);
}
/**
}
/**
- * Returns the time when the given was first followed by any local Sone.
- *
- * @param sone
- * The Sone to get the time for
- * @return The time (in milliseconds since Jan 1, 1970) the Sone has first
- * been followed, or {@link Long#MAX_VALUE}
- */
- public long getSoneFollowingTime(Sone sone) {
- synchronized (soneFollowingTimes) {
- return Optional.fromNullable(soneFollowingTimes.get(sone.getId())).or(Long.MAX_VALUE);
- }
- }
-
- /**
* Returns a post builder.
*
* @return A new post builder
return database.newPostBuilder();
}
- /**
- * {@inheritDoc}
- */
+ @Nullable
@Override
- public Optional<Post> getPost(String postId) {
+ public Post getPost(@Nonnull String postId) {
return database.getPost(postId);
}
/**
* {@inheritDoc}
*/
+ @Nullable
@Override
- public Optional<PostReply> getPostReply(String replyId) {
+ public PostReply getPostReply(String replyId) {
return database.getPostReply(replyId);
}
* @return The Sones that like the given post
*/
public Set<Sone> getLikes(Post post) {
- Set<Sone> sones = new HashSet<Sone>();
+ Set<Sone> sones = new HashSet<>();
for (Sone sone : getSones()) {
if (sone.getLikedPostIds().contains(post.getId())) {
sones.add(sone);
* @return The Sones that like the given reply
*/
public Set<Sone> getLikes(PostReply reply) {
- Set<Sone> sones = new HashSet<Sone>();
+ Set<Sone> sones = new HashSet<>();
for (Sone sone : getSones()) {
if (sone.getLikedReplyIds().contains(reply.getId())) {
sones.add(sone);
* otherwise
*/
public boolean isBookmarked(Post post) {
- return isPostBookmarked(post.getId());
- }
-
- /**
- * Returns whether the post with the given ID is bookmarked.
- *
- * @param id
- * The ID of the post to check
- * @return {@code true} if the post with the given ID is bookmarked,
- * {@code false} otherwise
- */
- public boolean isPostBookmarked(String id) {
- synchronized (bookmarkedPosts) {
- return bookmarkedPosts.contains(id);
- }
+ return database.isPostBookmarked(post);
}
/**
* @return All bookmarked posts
*/
public Set<Post> getBookmarkedPosts() {
- Set<Post> posts = new HashSet<Post>();
- synchronized (bookmarkedPosts) {
- for (String bookmarkedPostId : bookmarkedPosts) {
- Optional<Post> post = getPost(bookmarkedPostId);
- if (post.isPresent()) {
- posts.add(post.get());
- }
- }
- }
- return posts;
+ return database.getBookmarkedPosts();
}
public AlbumBuilder albumBuilder() {
* @return The album with the given ID, or {@code null} if no album with the
* given ID exists
*/
- public Album getAlbum(String albumId) {
- return database.getAlbum(albumId).orNull();
+ @Nullable
+ public Album getAlbum(@Nonnull String albumId) {
+ return database.getAlbum(albumId);
}
public ImageBuilder imageBuilder() {
* The ID of the image
* @return The image with the given ID
*/
+ @Nullable
public Image getImage(String imageId) {
return getImage(imageId, true);
}
* @return The image with the given ID, or {@code null} if none exists and
* none was created
*/
+ @Nullable
public Image getImage(String imageId, boolean create) {
- Optional<Image> image = database.getImage(imageId);
- if (image.isPresent()) {
- return image.get();
+ Image image = database.getImage(imageId);
+ if (image != null) {
+ return image;
}
if (!create) {
return null;
}
logger.info(String.format("Adding Sone from OwnIdentity: %s", ownIdentity));
Sone sone = database.newSoneBuilder().local().from(ownIdentity).build();
- sone.setLatestEdition(Numbers.safeParseLong(ownIdentity.getProperty("Sone.LatestEdition"), 0L));
- sone.setClient(new Client("Sone", SonePlugin.VERSION.toString()));
+ String property = fromNullable(ownIdentity.getProperty("Sone.LatestEdition")).or("0");
+ sone.setLatestEdition(fromNullable(tryParse(property)).or(0L));
+ sone.setClient(new Client("Sone", SonePlugin.getPluginVersion()));
sone.setKnown(true);
- /* TODO - load posts ’n stuff */
- SoneInserter soneInserter = new SoneInserter(this, eventBus, freenetInterface, ownIdentity.getId());
+ SoneInserter soneInserter = new SoneInserter(this, eventBus, freenetInterface, metricRegistry, ownIdentity.getId());
+ soneInserter.insertionDelayChanged(new InsertionDelayChangedEvent(preferences.getInsertionDelay()));
eventBus.register(soneInserter);
synchronized (soneInserters) {
soneInserters.put(sone, soneInserter);
}
loadSone(sone);
+ database.storeSone(sone);
sone.setStatus(SoneStatus.idle);
soneInserter.start();
return sone;
logger.log(Level.WARNING, "Given Identity is null!");
return null;
}
- final Long latestEdition = tryParse(fromNullable(
- identity.getProperty("Sone.LatestEdition")).or("0"));
- Optional<Sone> existingSone = getSone(identity.getId());
- if (existingSone.isPresent() && existingSone.get().isLocal()) {
- return existingSone.get();
+ String property = fromNullable(identity.getProperty("Sone.LatestEdition")).or("0");
+ long latestEdition = fromNullable(tryParse(property)).or(0L);
+ Sone existingSone = getSone(identity.getId());
+ if ((existingSone != null )&& existingSone.isLocal()) {
+ return existingSone;
}
- boolean newSone = !existingSone.isPresent();
- Sone sone = !newSone ? existingSone.get() : database.newSoneBuilder().from(identity).build();
+ boolean newSone = existingSone == null;
+ Sone sone = !newSone ? existingSone : database.newSoneBuilder().from(identity).build();
sone.setLatestEdition(latestEdition);
if (newSone) {
synchronized (knownSones) {
}
database.storeSone(sone);
soneDownloader.addSone(sone);
- soneDownloaders.execute(soneDownloader.fetchSoneWithUriAction(sone));
+ soneDownloaders.execute(soneDownloader.fetchSoneAsUskAction(sone));
return sone;
}
public void followSone(Sone sone, String soneId) {
checkNotNull(sone, "sone must not be null");
checkNotNull(soneId, "soneId must not be null");
- sone.addFriend(soneId);
- synchronized (soneFollowingTimes) {
- if (!soneFollowingTimes.containsKey(soneId)) {
- long now = System.currentTimeMillis();
- soneFollowingTimes.put(soneId, now);
- Optional<Sone> followedSone = getSone(soneId);
- if (!followedSone.isPresent()) {
- return;
- }
- for (Post post : followedSone.get().getPosts()) {
- if (post.getTime() < now) {
- markPostKnown(post);
- }
- }
- for (PostReply reply : followedSone.get().getReplies()) {
- if (reply.getTime() < now) {
- markReplyKnown(reply);
- }
- }
+ database.addFriend(sone, soneId);
+ @SuppressWarnings("ConstantConditions") // we just followed, this can’t be null.
+ long now = database.getFollowingTime(soneId);
+ Sone followedSone = getSone(soneId);
+ if (followedSone == null) {
+ return;
+ }
+ for (Post post : followedSone.getPosts()) {
+ if (post.getTime() < now) {
+ markPostKnown(post);
+ }
+ }
+ for (PostReply reply : followedSone.getReplies()) {
+ if (reply.getTime() < now) {
+ markReplyKnown(reply);
}
}
touchConfiguration();
public void unfollowSone(Sone sone, String soneId) {
checkNotNull(sone, "sone must not be null");
checkNotNull(soneId, "soneId must not be null");
- sone.removeFriend(soneId);
- boolean unfollowedSoneStillFollowed = false;
- for (Sone localSone : getLocalSones()) {
- unfollowedSoneStillFollowed |= localSone.hasFriend(soneId);
- }
- if (!unfollowedSoneStillFollowed) {
- synchronized (soneFollowingTimes) {
- soneFollowingTimes.remove(soneId);
- }
- }
+ database.removeFriend(sone, soneId);
touchConfiguration();
}
* of the age of the given Sone
*/
public void updateSone(final Sone sone, boolean soneRescueMode) {
- Optional<Sone> storedSone = getSone(sone.getId());
- if (storedSone.isPresent()) {
- if (!soneRescueMode && !(sone.getTime() > storedSone.get().getTime())) {
+ Sone storedSone = getSone(sone.getId());
+ if (storedSone != null) {
+ 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;
}
- /* find removed posts. */
- SoneChangeDetector soneChangeDetector = new SoneChangeDetector(storedSone.get());
- soneChangeDetector.onNewPosts(new PostProcessor() {
- @Override
- public void processPost(Post post) {
- if (post.getTime() < getSoneFollowingTime(sone)) {
- post.setKnown(true);
- } else if (!post.isKnown()) {
- eventBus.post(new NewPostFoundEvent(post));
- }
- }
- });
- soneChangeDetector.onRemovedPosts(new PostProcessor() {
- @Override
- public void processPost(Post post) {
- eventBus.post(new PostRemovedEvent(post));
- }
- });
- soneChangeDetector.onNewPostReplies(new PostReplyProcessor() {
- @Override
- public void processPostReply(PostReply postReply) {
- if (postReply.getTime() < getSoneFollowingTime(sone)) {
- postReply.setKnown(true);
- } else if (!postReply.isKnown()) {
- eventBus.post(new NewPostReplyFoundEvent(postReply));
- }
- }
- });
- soneChangeDetector.onRemovedPostReplies(new PostReplyProcessor() {
- @Override
- public void processPostReply(PostReply postReply) {
- eventBus.post(new PostReplyRemovedEvent(postReply));
- }
- });
- soneChangeDetector.detectChanges(sone);
+ List<Object> events =
+ collectEventsForChangesInSone(storedSone, sone);
database.storeSone(sone);
- sone.setOptions(storedSone.get().getOptions());
- sone.setKnown(storedSone.get().isKnown());
+ for (Object event : events) {
+ eventBus.post(event);
+ }
+ sone.setOptions(storedSone.getOptions());
+ sone.setKnown(storedSone.isKnown());
sone.setStatus((sone.getTime() == 0) ? SoneStatus.unknown : SoneStatus.idle);
if (sone.isLocal()) {
touchConfiguration();
}
}
+ private List<Object> collectEventsForChangesInSone(Sone oldSone, Sone newSone) {
+ List<Object> events = new ArrayList<>();
+ SoneComparison soneComparison = new SoneComparison(oldSone, newSone);
+ for (Post newPost : soneComparison.getNewPosts()) {
+ if (newPost.getSone().equals(newSone)) {
+ newPost.setKnown(true);
+ } else if (newPost.getTime() < database.getFollowingTime(newSone.getId())) {
+ newPost.setKnown(true);
+ } else if (!newPost.isKnown()) {
+ events.add(new NewPostFoundEvent(newPost));
+ }
+ }
+ for (Post post : soneComparison.getRemovedPosts()) {
+ events.add(new PostRemovedEvent(post));
+ }
+ for (PostReply postReply : soneComparison.getNewPostReplies()) {
+ if (postReply.getSone().equals(newSone)) {
+ postReply.setKnown(true);
+ } else if (postReply.getTime() < database.getFollowingTime(newSone.getId())) {
+ postReply.setKnown(true);
+ } else if (!postReply.isKnown()) {
+ events.add(new NewPostReplyFoundEvent(postReply));
+ }
+ }
+ for (PostReply postReply : soneComparison.getRemovedPostReplies()) {
+ events.add(new PostReplyRemovedEvent(postReply));
+ }
+ return events;
+ }
+
/**
* Deletes the given Sone. This will remove the Sone from the
* {@link #getLocalSones() local Sones}, stop its {@link SoneInserter} and
logger.log(Level.WARNING, String.format("Tried to delete non-local Sone: %s", sone));
return;
}
- // FIXME – implement in database
-// sones.remove(sone.getId());
SoneInserter soneInserter = soneInserters.remove(sone);
soneInserter.stop();
+ database.removeSone(sone);
webOfTrustUpdater.removeContext((OwnIdentity) sone.getIdentity(), "Sone");
webOfTrustUpdater.removeProperty((OwnIdentity) sone.getIdentity(), "Sone.LatestEdition");
try {
Set<String> likedReplyIds =
configurationSoneParser.parseLikedPostReplyIds();
- /* load friends. */
- Set<String> friends = configurationSoneParser.parseFriends();
-
/* load albums. */
List<Album> topLevelAlbums;
try {
}
/* load options. */
- sone.getOptions().setAutoFollow(configuration.getBooleanValue(sonePrefix + "/Options/AutoFollow").getValue(null));
- sone.getOptions().setSoneInsertNotificationEnabled(configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").getValue(null));
- sone.getOptions().setShowNewSoneNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewSones").getValue(null));
- sone.getOptions().setShowNewPostNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").getValue(null));
- sone.getOptions().setShowNewReplyNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").getValue(null));
- sone.getOptions().setShowCustomAvatars(ShowCustomAvatars.valueOf(configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars").getValue(ShowCustomAvatars.NEVER.name())));
+ sone.getOptions().setAutoFollow(configuration.getBooleanValue(sonePrefix + "/Options/AutoFollow").getValue(false));
+ sone.getOptions().setSoneInsertNotificationEnabled(configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").getValue(false));
+ sone.getOptions().setShowNewSoneNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewSones").getValue(true));
+ sone.getOptions().setShowNewPostNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").getValue(true));
+ sone.getOptions().setShowNewReplyNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").getValue(true));
+ sone.getOptions().setShowCustomAvatars(LoadExternalContent.valueOf(configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars").getValue(LoadExternalContent.NEVER.name())));
+ sone.getOptions().setLoadLinkedImages(LoadExternalContent.valueOf(configuration.getStringValue(sonePrefix + "/Options/LoadLinkedImages").getValue(LoadExternalContent.NEVER.name())));
/* if we’re still here, Sone was loaded successfully. */
synchronized (sone) {
sone.setReplies(replies);
sone.setLikePostIds(likedPostIds);
sone.setLikeReplyIds(likedReplyIds);
- for (String friendId : friends) {
- followSone(sone, friendId);
- }
for (Album album : sone.getRootAlbum().getAlbums()) {
sone.getRootAlbum().removeAlbum(album);
}
for (Album album : topLevelAlbums) {
sone.getRootAlbum().addAlbum(album);
}
- database.storeSone(sone);
synchronized (soneInserters) {
soneInserters.get(sone).setLastInsertFingerprint(lastInsertFingerprint);
}
}
- synchronized (knownSones) {
- for (String friend : friends) {
- knownSones.add(friend);
- }
- }
for (Post post : posts) {
post.setKnown(true);
}
* The text of the post
* @return The created post
*/
- public Post createPost(Sone sone, Optional<Sone> recipient, String text) {
- return createPost(sone, recipient, System.currentTimeMillis(), text);
- }
-
- /**
- * Creates a new post.
- *
- * @param sone
- * The Sone that creates the post
- * @param recipient
- * The recipient Sone, or {@code null} if this post does not have
- * a recipient
- * @param time
- * The time of the post
- * @param text
- * The text of the post
- * @return The created post
- */
- public Post createPost(Sone sone, Optional<Sone> recipient, long time, String text) {
+ public Post createPost(Sone sone, @Nullable Sone recipient, String text) {
checkNotNull(text, "text must not be null");
checkArgument(text.trim().length() > 0, "text must not be empty");
if (!sone.isLocal()) {
return null;
}
PostBuilder postBuilder = database.newPostBuilder();
- postBuilder.from(sone.getId()).randomId().withTime(time).withText(text.trim());
- if (recipient.isPresent()) {
- postBuilder.to(recipient.get().getId());
+ postBuilder.from(sone.getId()).randomId().currentTime().withText(text.trim());
+ if (recipient != null) {
+ postBuilder.to(recipient.getId());
}
final Post post = postBuilder.build();
database.storePost(post);
}
}
- /**
- * Bookmarks the post with the given ID.
- *
- * @param id
- * The ID of the post to bookmark
- */
- public void bookmarkPost(String id) {
- synchronized (bookmarkedPosts) {
- bookmarkedPosts.add(id);
- }
+ public void bookmarkPost(Post post) {
+ database.bookmarkPost(post);
}
/**
* @param post
* The post to unbookmark
*/
- public void unbookmark(Post post) {
- unbookmarkPost(post.getId());
- }
-
- /**
- * Removes the post with the given ID from the bookmarks.
- *
- * @param id
- * The ID of the post to unbookmark
- */
- public void unbookmarkPost(String id) {
- synchronized (bookmarkedPosts) {
- bookmarkedPosts.remove(id);
- }
+ public void unbookmarkPost(Post post) {
+ database.unbookmarkPost(post);
}
/**
identityManager.start();
webOfTrustUpdater.init();
webOfTrustUpdater.start();
- database.start();
+ database.startAsync();
}
/**
saveSone(soneInserter.getKey());
}
}
+ synchronized (soneRescuers) {
+ for (SoneRescuer soneRescuer : soneRescuers.values()) {
+ soneRescuer.stop();
+ }
+ }
saveConfiguration();
- database.stop();
+ database.stopAsync();
webOfTrustUpdater.stop();
updateChecker.stop();
soneDownloader.stop();
}
configuration.getStringValue(sonePrefix + "/Likes/Reply/" + replyLikeCounter + "/ID").setValue(null);
- /* save friends. */
- int friendCounter = 0;
- for (String friendId : sone.getFriends()) {
- configuration.getStringValue(sonePrefix + "/Friends/" + friendCounter++ + "/ID").setValue(friendId);
- }
- configuration.getStringValue(sonePrefix + "/Friends/" + friendCounter + "/ID").setValue(null);
-
/* save albums. first, collect in a flat structure, top-level first. */
List<Album> albums = FluentIterable.from(sone.getRootAlbum().getAlbums()).transformAndConcat(Album.FLATTENER).toList();
configuration.getStringValue(albumPrefix + "/Title").setValue(album.getTitle());
configuration.getStringValue(albumPrefix + "/Description").setValue(album.getDescription());
configuration.getStringValue(albumPrefix + "/Parent").setValue(album.getParent().equals(sone.getRootAlbum()) ? null : album.getParent().getId());
- configuration.getStringValue(albumPrefix + "/AlbumImage").setValue(album.getAlbumImage() == null ? null : album.getAlbumImage().getId());
}
configuration.getStringValue(sonePrefix + "/Albums/" + albumCounter + "/ID").setValue(null);
configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").setValue(sone.getOptions().isShowNewPostNotifications());
configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").setValue(sone.getOptions().isShowNewReplyNotifications());
configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars").setValue(sone.getOptions().getShowCustomAvatars().name());
-
- configuration.save();
+ configuration.getStringValue(sonePrefix + "/Options/LoadLinkedImages").setValue(sone.getOptions().getLoadLinkedImages().name());
webOfTrustUpdater.setProperty((OwnIdentity) sone.getIdentity(), "Sone.LatestEdition", String.valueOf(sone.getLatestEdition()));
/* store the options first. */
try {
- configuration.getIntValue("Option/ConfigurationVersion").setValue(0);
- configuration.getIntValue("Option/InsertionDelay").setValue(options.getIntegerOption("InsertionDelay").getReal());
- configuration.getIntValue("Option/PostsPerPage").setValue(options.getIntegerOption("PostsPerPage").getReal());
- configuration.getIntValue("Option/ImagesPerPage").setValue(options.getIntegerOption("ImagesPerPage").getReal());
- configuration.getIntValue("Option/CharactersPerPost").setValue(options.getIntegerOption("CharactersPerPost").getReal());
- configuration.getIntValue("Option/PostCutOffLength").setValue(options.getIntegerOption("PostCutOffLength").getReal());
- configuration.getBooleanValue("Option/RequireFullAccess").setValue(options.getBooleanOption("RequireFullAccess").getReal());
- configuration.getIntValue("Option/PositiveTrust").setValue(options.getIntegerOption("PositiveTrust").getReal());
- configuration.getIntValue("Option/NegativeTrust").setValue(options.getIntegerOption("NegativeTrust").getReal());
- configuration.getStringValue("Option/TrustComment").setValue(options.getStringOption("TrustComment").getReal());
- configuration.getBooleanValue("Option/ActivateFcpInterface").setValue(options.getBooleanOption("ActivateFcpInterface").getReal());
- configuration.getIntValue("Option/FcpFullAccessRequired").setValue(options.getIntegerOption("FcpFullAccessRequired").getReal());
+ preferences.saveTo(configuration);
/* save known Sones. */
int soneCounter = 0;
configuration.getStringValue("KnownSone/" + soneCounter + "/ID").setValue(null);
}
- /* save Sone following times. */
- soneCounter = 0;
- synchronized (soneFollowingTimes) {
- for (Entry<String, Long> soneFollowingTime : soneFollowingTimes.entrySet()) {
- configuration.getStringValue("SoneFollowingTimes/" + soneCounter + "/Sone").setValue(soneFollowingTime.getKey());
- configuration.getLongValue("SoneFollowingTimes/" + soneCounter + "/Time").setValue(soneFollowingTime.getValue());
- ++soneCounter;
- }
- configuration.getStringValue("SoneFollowingTimes/" + soneCounter + "/Sone").setValue(null);
- }
-
/* save known posts. */
database.save();
- /* save bookmarked posts. */
- int bookmarkedPostCounter = 0;
- synchronized (bookmarkedPosts) {
- for (String bookmarkedPostId : bookmarkedPosts) {
- configuration.getStringValue("Bookmarks/Post/" + bookmarkedPostCounter++ + "/ID").setValue(bookmarkedPostId);
- }
- }
- configuration.getStringValue("Bookmarks/Post/" + bookmarkedPostCounter++ + "/ID").setValue(null);
-
/* now save it. */
configuration.save();
* Loads the configuration.
*/
private void loadConfiguration() {
- /* create options. */
- options.addIntegerOption("InsertionDelay", new DefaultOption<Integer>(60, new IntegerRangePredicate(0, Integer.MAX_VALUE)));
- options.addIntegerOption("PostsPerPage", new DefaultOption<Integer>(10, new IntegerRangePredicate(1, Integer.MAX_VALUE)));
- options.addIntegerOption("ImagesPerPage", new DefaultOption<Integer>(9, new IntegerRangePredicate(1, Integer.MAX_VALUE)));
- options.addIntegerOption("CharactersPerPost", new DefaultOption<Integer>(400, Predicates.<Integer> or(new IntegerRangePredicate(50, Integer.MAX_VALUE), Predicates.equalTo(-1))));
- options.addIntegerOption("PostCutOffLength", new DefaultOption<Integer>(200, Predicates.<Integer> or(new IntegerRangePredicate(50, Integer.MAX_VALUE), Predicates.equalTo(-1))));
- options.addBooleanOption("RequireFullAccess", new DefaultOption<Boolean>(false));
- options.addIntegerOption("PositiveTrust", new DefaultOption<Integer>(75, new IntegerRangePredicate(0, 100)));
- options.addIntegerOption("NegativeTrust", new DefaultOption<Integer>(-25, new IntegerRangePredicate(-100, 100)));
- options.addStringOption("TrustComment", new DefaultOption<String>("Set from Sone Web Interface"));
- options.addBooleanOption("ActivateFcpInterface", new DefaultOption<Boolean>(false));
- options.addIntegerOption("FcpFullAccessRequired", new DefaultOption<Integer>(2, fcpInterface.new SetFullAccessRequired()));
-
- loadConfigurationValue("InsertionDelay");
- loadConfigurationValue("PostsPerPage");
- loadConfigurationValue("ImagesPerPage");
- loadConfigurationValue("CharactersPerPost");
- loadConfigurationValue("PostCutOffLength");
- options.getBooleanOption("RequireFullAccess").set(configuration.getBooleanValue("Option/RequireFullAccess").getValue(null));
- loadConfigurationValue("PositiveTrust");
- loadConfigurationValue("NegativeTrust");
- options.getStringOption("TrustComment").set(configuration.getStringValue("Option/TrustComment").getValue(null));
- options.getBooleanOption("ActivateFcpInterface").set(configuration.getBooleanValue("Option/ActivateFcpInterface").getValue(null));
- options.getIntegerOption("FcpFullAccessRequired").set(configuration.getIntValue("Option/FcpFullAccessRequired").getValue(null));
+ new PreferencesLoader(preferences).loadFrom(configuration);
/* load known Sones. */
int soneCounter = 0;
knownSones.add(knownSoneId);
}
}
-
- /* load Sone following times. */
- soneCounter = 0;
- while (true) {
- String soneId = configuration.getStringValue("SoneFollowingTimes/" + soneCounter + "/Sone").getValue(null);
- if (soneId == null) {
- break;
- }
- long time = configuration.getLongValue("SoneFollowingTimes/" + soneCounter + "/Time").getValue(Long.MAX_VALUE);
- synchronized (soneFollowingTimes) {
- soneFollowingTimes.put(soneId, time);
- }
- ++soneCounter;
- }
-
- /* load bookmarked posts. */
- int bookmarkedPostCounter = 0;
- while (true) {
- String bookmarkedPostId = configuration.getStringValue("Bookmarks/Post/" + bookmarkedPostCounter++ + "/ID").getValue(null);
- if (bookmarkedPostId == null) {
- break;
- }
- synchronized (bookmarkedPosts) {
- bookmarkedPosts.add(bookmarkedPostId);
- }
- }
-
- }
-
- /**
- * Loads an {@link Integer} configuration value for the option with the
- * given name, logging validation failures.
- *
- * @param optionName
- * The name of the option to load
- */
- private void loadConfigurationValue(String optionName) {
- try {
- options.getIntegerOption(optionName).set(configuration.getIntValue("Option/" + optionName).getValue(null));
- } catch (IllegalArgumentException iae1) {
- logger.log(Level.WARNING, String.format("Invalid value for %s in configuration, using default.", optionName));
- }
}
/**
if (sone.isLocal()) {
return;
}
- sone.setLatestEdition(Numbers.safeParseLong(identity.getProperty("Sone.LatestEdition"), sone.getLatestEdition()));
+ String newLatestEdition = identity.getProperty("Sone.LatestEdition");
+ if (newLatestEdition != null) {
+ Long parsedNewLatestEdition = tryParse(newLatestEdition);
+ if (parsedNewLatestEdition != null) {
+ sone.setLatestEdition(parsedNewLatestEdition);
+ }
+ }
soneDownloader.addSone(sone);
- soneDownloaders.execute(soneDownloader.fetchSoneAction(sone));
+ soneDownloaders.execute(soneDownloader.fetchSoneAsSskAction(sone));
}
/**
return;
}
}
- Optional<Sone> sone = getSone(identity.getId());
- if (!sone.isPresent()) {
+ Sone sone = getSone(identity.getId());
+ if (sone == null) {
/* TODO - we don’t have the Sone anymore. should this happen? */
return;
}
- database.removePosts(sone.get());
- for (Post post : sone.get().getPosts()) {
- eventBus.post(new PostRemovedEvent(post));
+ for (PostReply postReply : sone.getReplies()) {
+ eventBus.post(new PostReplyRemovedEvent(postReply));
}
- database.removePostReplies(sone.get());
- for (PostReply reply : sone.get().getReplies()) {
- eventBus.post(new PostReplyRemovedEvent(reply));
+ for (Post post : sone.getPosts()) {
+ eventBus.post(new PostRemovedEvent(post));
}
-// TODO – implement in database
-// sones.remove(identity.getId());
- eventBus.post(new SoneRemovedEvent(sone.get()));
+ eventBus.post(new SoneRemovedEvent(sone));
+ database.removeSone(sone);
}
/**
*/
@Subscribe
public void imageInsertFinished(ImageInsertFinishedEvent imageInsertFinishedEvent) {
- logger.log(Level.WARNING, String.format("Image insert finished for %s: %s", imageInsertFinishedEvent.image(), imageInsertFinishedEvent.resultingUri()));
- imageInsertFinishedEvent.image().modify().setKey(imageInsertFinishedEvent.resultingUri().toString()).update();
- deleteTemporaryImage(imageInsertFinishedEvent.image().getId());
+ logger.log(Level.WARNING, String.format("Image insert finished for %s: %s", imageInsertFinishedEvent.getImage(), imageInsertFinishedEvent.getResultingUri()));
+ imageInsertFinishedEvent.getImage().modify().setKey(imageInsertFinishedEvent.getResultingUri().toString()).update();
+ deleteTemporaryImage(imageInsertFinishedEvent.getImage().getId());
touchConfiguration();
}