X-Git-Url: https://git.pterodactylus.net/?a=blobdiff_plain;f=src%2Fmain%2Fjava%2Fnet%2Fpterodactylus%2Fsone%2Fdata%2Fimpl%2FDefaultSone.java;h=92d7d5fd735431b094f11eab8d03666754c9dd29;hb=f3de529dac23aff1f2378930cc5b68f43450a6b3;hp=bbf7a2d5e3d826c321b2447fe5419e9c70f1a66c;hpb=9d9e7df5ad1cb5646be2caa666099f8c40a45ee3;p=Sone.git diff --git a/src/main/java/net/pterodactylus/sone/data/impl/DefaultSone.java b/src/main/java/net/pterodactylus/sone/data/impl/DefaultSone.java index bbf7a2d..92d7d5f 100644 --- a/src/main/java/net/pterodactylus/sone/data/impl/DefaultSone.java +++ b/src/main/java/net/pterodactylus/sone/data/impl/DefaultSone.java @@ -17,6 +17,8 @@ package net.pterodactylus.sone.data.impl; +import static com.google.common.base.Preconditions.checkNotNull; + import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -24,18 +26,75 @@ import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.logging.Level; +import java.util.logging.Logger; +import net.pterodactylus.sone.core.Options; +import net.pterodactylus.sone.data.Album; +import net.pterodactylus.sone.data.Client; import net.pterodactylus.sone.data.Post; import net.pterodactylus.sone.data.PostReply; +import net.pterodactylus.sone.data.Profile; +import net.pterodactylus.sone.data.Reply; import net.pterodactylus.sone.data.Sone; import net.pterodactylus.sone.database.AlbumBuilder; +import net.pterodactylus.sone.database.Database; +import net.pterodactylus.sone.database.PostBuilder; +import net.pterodactylus.sone.database.PostReplyBuilder; +import net.pterodactylus.sone.freenet.wot.Identity; +import net.pterodactylus.util.logging.Logging; + +import freenet.keys.FreenetURI; + +import com.google.common.base.Optional; +import com.google.common.hash.Hasher; +import com.google.common.hash.Hashing; /** * Dumb, store-everything-in-memory {@link Sone} implementation. * * @author David ‘Bombe’ Roden */ -public class DefaultSone extends AbstractSone { +public class DefaultSone implements Sone { + + /** The logger. */ + private static final Logger logger = Logging.getLogger(DefaultSone.class); + + /** The database. */ + private final Database database; + + /** The ID of this Sone. */ + private final String id; + + /** Whether the Sone is local. */ + private final boolean local; + + /** The URI under which the Sone is stored in Freenet. */ + private volatile FreenetURI requestUri; + + /** The URI used to insert a new version of this Sone. */ + /* This will be null for remote Sones! */ + private volatile FreenetURI insertUri; + + /** The latest edition of the Sone. */ + private volatile long latestEdition; + + /** The time of the last inserted update. */ + private volatile long time; + + /** The status of this Sone. */ + private volatile SoneStatus status = SoneStatus.unknown; + + /** The profile of this Sone. */ + private volatile Profile profile = new Profile(this); + + /** The client used by the Sone. */ + private final Client client; + + /** Whether this Sone is known. */ + private volatile boolean known; + + /** All friend Sones. */ + private final Set friendSones = new CopyOnWriteArraySet(); /** All posts. */ private final Set posts = new CopyOnWriteArraySet(); @@ -49,6 +108,12 @@ public class DefaultSone extends AbstractSone { /** The IDs of all liked replies. */ private final Set likedReplyIds = new CopyOnWriteArraySet(); + /** The root album containing all albums. */ + private final Album rootAlbum; + + /** Sone-specific options. */ + private Options options = new Options(); + /** * Creates a new Sone. * @@ -57,64 +122,126 @@ public class DefaultSone extends AbstractSone { * @param local * {@code true} if the Sone is a local Sone, {@code false} otherwise */ - public DefaultSone(String id, boolean local) { - super(id, local); + public DefaultSone(Database database, String id, boolean local, Client client) { + this.database = database; + this.id = id; + this.local = local; + this.client = client; + rootAlbum = new DefaultAlbumBuilder(database, this, null).build(); } // // ACCESSORS // - /** - * Returns whether this Sone is known. - * - * @return {@code true} if this Sone is known, {@code false} otherwise - */ + public String getId() { + return id; + } + + public Identity getIdentity() { + return database.getIdentity(id).get(); + } + + public String getName() { + return getIdentity().getNickname(); + } + + public boolean isLocal() { + return local; + } + + public FreenetURI getRequestUri() { + return (requestUri != null) ? requestUri.setSuggestedEdition(latestEdition) : null; + } + + public Sone setRequestUri(FreenetURI requestUri) { + if (this.requestUri == null) { + this.requestUri = requestUri.setKeyType("USK").setDocName("Sone").setMetaString(new String[0]); + return this; + } + if (!this.requestUri.equalsKeypair(requestUri)) { + logger.log(Level.WARNING, String.format("Request URI %s tried to overwrite %s!", requestUri, this.requestUri)); + return this; + } + return this; + } + + public FreenetURI getInsertUri() { + return (insertUri != null) ? insertUri.setSuggestedEdition(latestEdition) : null; + } + + public Sone setInsertUri(FreenetURI insertUri) { + if (this.insertUri == null) { + this.insertUri = insertUri.setKeyType("USK").setDocName("Sone").setMetaString(new String[0]); + return this; + } + if (!this.insertUri.equalsKeypair(insertUri)) { + logger.log(Level.WARNING, String.format("Request URI %s tried to overwrite %s!", insertUri, this.insertUri)); + return this; + } + return this; + } + + public long getLatestEdition() { + return latestEdition; + } + + public void setLatestEdition(long latestEdition) { + if (!(latestEdition > this.latestEdition)) { + logger.log(Level.FINE, String.format("New latest edition %d is not greater than current latest edition %d!", latestEdition, this.latestEdition)); + return; + } + this.latestEdition = latestEdition; + } + + public long getTime() { + return time; + } + + public Sone setTime(long time) { + this.time = time; + return this; + } + + public SoneStatus getStatus() { + return status; + } + + public Sone setStatus(SoneStatus status) { + this.status = checkNotNull(status, "status must not be null"); + return this; + } + + public Profile getProfile() { + return new Profile(profile); + } + + public void setProfile(Profile profile) { + this.profile = new Profile(profile); + } + + public Client getClient() { + return client; + } + public boolean isKnown() { return known; } - /** - * Sets whether this Sone is known. - * - * @param known - * {@code true} if this Sone is known, {@code false} otherwise - * @return This Sone - */ public Sone setKnown(boolean known) { this.known = known; return this; } - /** - * Returns all friend Sones of this Sone. - * - * @return The friend Sones of this Sone - */ public List getFriends() { List friends = new ArrayList(friendSones); return friends; } - /** - * Returns whether this Sone has the given Sone as a friend Sone. - * - * @param friendSoneId - * The ID of the Sone to check for - * @return {@code true} if this Sone has the given Sone as a friend, {@code - * false} otherwise - */ public boolean hasFriend(String friendSoneId) { return friendSones.contains(friendSoneId); } - /** - * Adds the given Sone as a friend Sone. - * - * @param friendSone - * The friend Sone to add - * @return This Sone (for method chaining) - */ public Sone addFriend(String friendSone) { if (!friendSone.equals(id)) { friendSones.add(friendSone); @@ -122,23 +249,11 @@ public class DefaultSone extends AbstractSone { return this; } - /** - * Removes the given Sone as a friend Sone. - * - * @param friendSoneId - * The ID of the friend Sone to remove - * @return This Sone (for method chaining) - */ public Sone removeFriend(String friendSoneId) { friendSones.remove(friendSoneId); return this; } - /** - * Returns the list of posts of this Sone, sorted by time, newest first. - * - * @return All posts of this Sone - */ public List getPosts() { List sortedPosts; synchronized (this) { @@ -148,13 +263,6 @@ public class DefaultSone extends AbstractSone { return sortedPosts; } - /** - * Sets all posts of this Sone at once. - * - * @param posts - * The new (and only) posts of this Sone - * @return This Sone (for method chaining) - */ public Sone setPosts(Collection posts) { synchronized (this) { this.posts.clear(); @@ -163,94 +271,44 @@ public class DefaultSone extends AbstractSone { return this; } - /** - * Adds the given post to this Sone. The post will not be added if its {@link - * Post#getSone() Sone} is not this Sone. - * - * @param post - * The post to add - */ public void addPost(Post post) { if (post.getSone().equals(this) && posts.add(post)) { logger.log(Level.FINEST, String.format("Adding %s to “%s”.", post, getName())); } } - /** - * Removes the given post from this Sone. - * - * @param post - * The post to remove - */ public void removePost(Post post) { if (post.getSone().equals(this)) { posts.remove(post); } } - /** - * Returns all replies this Sone made. - * - * @return All replies this Sone made - */ public Set getReplies() { return Collections.unmodifiableSet(replies); } - /** - * Sets all replies of this Sone at once. - * - * @param replies - * The new (and only) replies of this Sone - * @return This Sone (for method chaining) - */ public Sone setReplies(Collection replies) { this.replies.clear(); this.replies.addAll(replies); return this; } - /** - * Adds a reply to this Sone. If the given reply was not made by this Sone, - * nothing is added to this Sone. - * - * @param reply - * The reply to add - */ public void addReply(PostReply reply) { if (reply.getSone().equals(this)) { replies.add(reply); } } - /** - * Removes a reply from this Sone. - * - * @param reply - * The reply to remove - */ public void removeReply(PostReply reply) { if (reply.getSone().equals(this)) { replies.remove(reply); } } - /** - * Returns the IDs of all liked posts. - * - * @return All liked posts’ IDs - */ public Set getLikedPostIds() { return Collections.unmodifiableSet(likedPostIds); } - /** - * Sets the IDs of all liked posts. - * - * @param likedPostIds - * All liked posts’ IDs - * @return This Sone (for method chaining) - */ public Sone setLikePostIds(Set likedPostIds) { this.likedPostIds.clear(); this.likedPostIds.addAll(likedPostIds); @@ -295,9 +353,144 @@ public class DefaultSone extends AbstractSone { return this; } + public Album getRootAlbum() { + return rootAlbum; + } + + public Options getOptions() { + return options; + } + + /* TODO - remove this method again, maybe add an option provider */ + public void setOptions(Options options) { + this.options = options; + } + @Override public AlbumBuilder newAlbumBuilder() { - return new DefaultAlbumBuilder(this, rootAlbum); + return new DefaultAlbumBuilder(database, this, rootAlbum.getId()); + } + + public PostBuilder newPostBuilder() { + return new DefaultPostBuilder(database, getId()) { + @Override + public Post build(Optional postCreated) { + Post post = super.build(postCreated); + database.storePost(post); + return post; + } + }; + } + + @Override + public PostReplyBuilder newPostReplyBuilder(String postId) throws IllegalStateException { + return new DefaultPostReplyBuilder(database, getId(), postId) { + @Override + public PostReply build(Optional postReplyCreated) { + PostReply postReply = super.build(postReplyCreated); + database.storePostReply(postReply); + return postReply; + } + }; + } + + public Modifier modify() { + return new Modifier() { + private long latestEdition = DefaultSone.this.latestEdition; + @Override + public Modifier setLatestEdition(long latestEdition) { + this.latestEdition = latestEdition; + return this; + } + + @Override + public Sone update() { + DefaultSone.this.latestEdition = latestEdition; + return DefaultSone.this; + } + }; + } + + // + // FINGERPRINTABLE METHODS + // + + @Override + public synchronized String getFingerprint() { + Hasher hash = Hashing.sha256().newHasher(); + hash.putString(profile.getFingerprint()); + + hash.putString("Posts("); + for (Post post : getPosts()) { + hash.putString("Post(").putString(post.getId()).putString(")"); + } + hash.putString(")"); + + List replies = new ArrayList(getReplies()); + Collections.sort(replies, Reply.TIME_COMPARATOR); + hash.putString("Replies("); + for (PostReply reply : replies) { + hash.putString("Reply(").putString(reply.getId()).putString(")"); + } + hash.putString(")"); + + List likedPostIds = new ArrayList(getLikedPostIds()); + Collections.sort(likedPostIds); + hash.putString("LikedPosts("); + for (String likedPostId : likedPostIds) { + hash.putString("Post(").putString(likedPostId).putString(")"); + } + hash.putString(")"); + + List likedReplyIds = new ArrayList(getLikedReplyIds()); + Collections.sort(likedReplyIds); + hash.putString("LikedReplies("); + for (String likedReplyId : likedReplyIds) { + hash.putString("Reply(").putString(likedReplyId).putString(")"); + } + hash.putString(")"); + + hash.putString("Albums("); + for (Album album : rootAlbum.getAlbums()) { + if (!Album.NOT_EMPTY.apply(album)) { + continue; + } + hash.putString(album.getFingerprint()); + } + hash.putString(")"); + + return hash.hash().toString(); + } + + // + // INTERFACE Comparable + // + + @Override + public int compareTo(Sone sone) { + return NICE_NAME_COMPARATOR.compare(this, sone); + } + + // + // OBJECT METHODS + // + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof Sone)) { + return false; + } + return ((Sone) object).getId().equals(id); + } + + @Override + public String toString() { + return getClass().getName() + "[id=" + id + ",requestUri=" + requestUri + ",insertUri(" + String.valueOf(insertUri).length() + "),friends(" + friendSones.size() + "),posts(" + posts.size() + "),replies(" + replies.size() + "),albums(" + getRootAlbum().getAlbums().size() + ")]"; } }