mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-11 18:29:05 +01:00
Merge branch '436-automatic-personal-blogs-backend' into 'master'
Backend for Automatic Micro Blogs This MR introduces automatic personal blogs to the Blog client. When a contact is added, her personal blog will also be added automatically. Also, when a new identity is added, a personal blog for that identity is created. The first commit changes the blog paradigm to short-form blogs and introduces other things that will be useful later in the UI (!214) such as a BlogPostAdded event and the possibility to delete/remove blogs (not the personal ones). This MR is based on !224 to prevent crashes that are fixed by it. So please review and merge !224 first. See merge request !223
This commit is contained in:
@@ -11,13 +11,16 @@ public class Blog extends Forum {
|
||||
private final String description;
|
||||
@NotNull
|
||||
private final Author author;
|
||||
private final boolean permanent;
|
||||
|
||||
public Blog(@NotNull Group group, @NotNull String name,
|
||||
@NotNull String description, @NotNull Author author) {
|
||||
@NotNull String description, @NotNull Author author,
|
||||
boolean permanent) {
|
||||
super(group, name, null);
|
||||
|
||||
this.description = description;
|
||||
this.author = author;
|
||||
this.permanent = permanent;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -29,4 +32,8 @@ public class Blog extends Forum {
|
||||
public Author getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public boolean isPermanent() {
|
||||
return permanent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,12 @@ public interface BlogConstants {
|
||||
/** The length of a blog post's title in UTF-8 bytes. */
|
||||
int MAX_BLOG_POST_TITLE_LENGTH = 100;
|
||||
|
||||
/** The length of a blog post's teaser in UTF-8 bytes. */
|
||||
int MAX_BLOG_POST_TEASER_LENGTH = 240;
|
||||
|
||||
/** The maximum length of a blog post's body in bytes. */
|
||||
int MAX_BLOG_POST_BODY_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
|
||||
|
||||
/** The internal name of personal blogs that are created automatically */
|
||||
String PERSONAL_BLOG_NAME = "briar.PERSONAL_BLOG_NAME";
|
||||
|
||||
/* Blog Sharing Constants */
|
||||
String BLOG_TITLE = "blogTitle";
|
||||
String BLOG_DESC = "blogDescription";
|
||||
@@ -31,9 +31,8 @@ public interface BlogConstants {
|
||||
// Metadata keys
|
||||
String KEY_DESCRIPTION = "description";
|
||||
String KEY_TITLE = "title";
|
||||
String KEY_TEASER = "teaser";
|
||||
String KEY_HAS_BODY = "hasBody";
|
||||
String KEY_TIMESTAMP = "timestamp";
|
||||
String KEY_TIME_RECEIVED = "timeReceived";
|
||||
String KEY_PARENT = "parent";
|
||||
String KEY_AUTHOR_ID = "id";
|
||||
String KEY_AUTHOR_NAME = "name";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.api.blogs;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.contact.Contact;
|
||||
import org.briarproject.api.identity.Author;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -11,6 +12,9 @@ public interface BlogFactory {
|
||||
Blog createBlog(@NotNull String name, @NotNull String description,
|
||||
@NotNull Author author);
|
||||
|
||||
/** Creates a personal blog for a given author. */
|
||||
Blog createPersonalBlog(@NotNull Author author);
|
||||
|
||||
/** Parses a blog with the given Group and description */
|
||||
Blog parseBlog(@NotNull Group g, @NotNull String description)
|
||||
throws FormatException;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.briarproject.api.blogs;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.db.Transaction;
|
||||
import org.briarproject.api.identity.Author;
|
||||
import org.briarproject.api.identity.LocalAuthor;
|
||||
import org.briarproject.api.sync.ClientId;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
@@ -20,6 +20,9 @@ public interface BlogManager {
|
||||
Blog addBlog(LocalAuthor localAuthor, String name, String description)
|
||||
throws DbException;
|
||||
|
||||
/** Removes and deletes a blog. */
|
||||
void removeBlog(Blog b) throws DbException;
|
||||
|
||||
/** Stores a local blog post. */
|
||||
void addLocalPost(BlogPost p) throws DbException;
|
||||
|
||||
@@ -29,9 +32,12 @@ public interface BlogManager {
|
||||
/** Returns the blog with the given ID. */
|
||||
Blog getBlog(Transaction txn, GroupId g) throws DbException;
|
||||
|
||||
/** Returns all blogs to which the localAuthor created. */
|
||||
/** Returns all blogs owned by the given localAuthor. */
|
||||
Collection<Blog> getBlogs(LocalAuthor localAuthor) throws DbException;
|
||||
|
||||
/** Returns only the personal blog of the given author. */
|
||||
Blog getPersonalBlog(Author author) throws DbException;
|
||||
|
||||
/** Returns all blogs to which the user subscribes. */
|
||||
Collection<Blog> getBlogs() throws DbException;
|
||||
|
||||
@@ -45,4 +51,11 @@ public interface BlogManager {
|
||||
/** Marks a blog post as read or unread. */
|
||||
void setReadFlag(MessageId m, boolean read) throws DbException;
|
||||
|
||||
/** Registers a hook to be called whenever a blog is removed. */
|
||||
void registerRemoveBlogHook(RemoveBlogHook hook);
|
||||
|
||||
interface RemoveBlogHook {
|
||||
void removingBlog(Transaction txn, Blog b) throws DbException;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,43 +1,27 @@
|
||||
package org.briarproject.api.blogs;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import org.briarproject.api.forum.ForumPost;
|
||||
import org.briarproject.api.identity.Author;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class BlogPost extends ForumPost {
|
||||
|
||||
@Nullable
|
||||
private final String title;
|
||||
@NotNull
|
||||
private final String teaser;
|
||||
private final boolean hasBody;
|
||||
|
||||
public BlogPost(@Nullable String title, @NotNull String teaser,
|
||||
boolean hasBody, @NotNull Message message,
|
||||
@Nullable MessageId parent, @NotNull Author author,
|
||||
public BlogPost(@Nullable String title, @NotNull Message message,
|
||||
@Nullable MessageId parent, @NotNull Author author,
|
||||
@NotNull String contentType) {
|
||||
super(message, parent, author, contentType);
|
||||
|
||||
this.title = title;
|
||||
this.teaser = teaser;
|
||||
this.hasBody = hasBody;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getTeaser() {
|
||||
return teaser;
|
||||
}
|
||||
|
||||
public boolean hasBody() {
|
||||
return hasBody;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ import java.security.GeneralSecurityException;
|
||||
public interface BlogPostFactory {
|
||||
|
||||
BlogPost createBlogPost(@NotNull GroupId groupId, @Nullable String title,
|
||||
@NotNull String teaser, long timestamp, @Nullable MessageId parent,
|
||||
long timestamp, @Nullable MessageId parent,
|
||||
@NotNull LocalAuthor author, @NotNull String contentType,
|
||||
@Nullable byte[] body)
|
||||
@NotNull byte[] body)
|
||||
throws FormatException, GeneralSecurityException;
|
||||
}
|
||||
|
||||
@@ -11,20 +11,16 @@ public class BlogPostHeader extends PostHeader {
|
||||
|
||||
@Nullable
|
||||
private final String title;
|
||||
@NotNull
|
||||
private final String teaser;
|
||||
private final boolean hasBody;
|
||||
private final long timeReceived;
|
||||
|
||||
public BlogPostHeader(@Nullable String title, @NotNull String teaser,
|
||||
boolean hasBody, @NotNull MessageId id,
|
||||
@Nullable MessageId parentId, long timestamp,
|
||||
public BlogPostHeader(@Nullable String title, @NotNull MessageId id,
|
||||
@Nullable MessageId parentId, long timestamp, long timeReceived,
|
||||
@NotNull Author author, @NotNull Status authorStatus,
|
||||
@NotNull String contentType, boolean read) {
|
||||
super(id, parentId, timestamp, author, authorStatus, contentType, read);
|
||||
|
||||
this.title = title;
|
||||
this.teaser = teaser;
|
||||
this.hasBody = hasBody;
|
||||
this.timeReceived = timeReceived;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -32,12 +28,8 @@ public class BlogPostHeader extends PostHeader {
|
||||
return title;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getTeaser() {
|
||||
return teaser;
|
||||
public long getTimeReceived() {
|
||||
return timeReceived;
|
||||
}
|
||||
|
||||
public boolean hasBody() {
|
||||
return hasBody;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.briarproject.api.event;
|
||||
|
||||
import org.briarproject.api.blogs.BlogPostHeader;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
|
||||
/** An event that is broadcast when a blog post was added to the database. */
|
||||
public class BlogPostAddedEvent extends Event {
|
||||
|
||||
private final GroupId groupId;
|
||||
private final BlogPostHeader header;
|
||||
private final boolean local;
|
||||
|
||||
public BlogPostAddedEvent(GroupId groupId, BlogPostHeader header,
|
||||
boolean local) {
|
||||
|
||||
this.groupId = groupId;
|
||||
this.header = header;
|
||||
this.local = local;
|
||||
}
|
||||
|
||||
public GroupId getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public BlogPostHeader getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
public boolean isLocal() {
|
||||
return local;
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,9 @@ public interface IdentityManager {
|
||||
/** Returns the trust-level status of the author */
|
||||
Status getAuthorStatus(AuthorId a) throws DbException;
|
||||
|
||||
/** Returns the trust-level status of the author */
|
||||
Status getAuthorStatus(Transaction txn, AuthorId a) throws DbException;
|
||||
|
||||
interface AddIdentityHook {
|
||||
void addingIdentity(Transaction txn, LocalAuthor a) throws DbException;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.blogs.Blog;
|
||||
import org.briarproject.api.blogs.BlogFactory;
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
import org.briarproject.api.contact.Contact;
|
||||
import org.briarproject.api.data.BdfList;
|
||||
import org.briarproject.api.identity.Author;
|
||||
import org.briarproject.api.identity.AuthorFactory;
|
||||
@@ -13,6 +14,8 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.api.blogs.BlogConstants.PERSONAL_BLOG_NAME;
|
||||
|
||||
class BlogFactoryImpl implements BlogFactory {
|
||||
|
||||
private final GroupFactory groupFactory;
|
||||
@@ -31,6 +34,16 @@ class BlogFactoryImpl implements BlogFactory {
|
||||
@Override
|
||||
public Blog createBlog(@NotNull String name, @NotNull String description,
|
||||
@NotNull Author author) {
|
||||
return createBlog(name, description, author, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Blog createPersonalBlog(@NotNull Author a) {
|
||||
return createBlog(PERSONAL_BLOG_NAME, "", a, true);
|
||||
}
|
||||
|
||||
private Blog createBlog(@NotNull String name, @NotNull String description,
|
||||
@NotNull Author author, boolean permanent) {
|
||||
try {
|
||||
BdfList blog = BdfList.of(
|
||||
name,
|
||||
@@ -40,7 +53,7 @@ class BlogFactoryImpl implements BlogFactory {
|
||||
byte[] descriptor = clientHelper.toByteArray(blog);
|
||||
Group g = groupFactory
|
||||
.createGroup(BlogManagerImpl.CLIENT_ID, descriptor);
|
||||
return new Blog(g, name, description, author);
|
||||
return new Blog(g, name, description, author, permanent);
|
||||
} catch (FormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -55,7 +68,9 @@ class BlogFactoryImpl implements BlogFactory {
|
||||
BdfList blog = clientHelper.toList(descriptor, 0, descriptor.length);
|
||||
Author a =
|
||||
authorFactory.createAuthor(blog.getString(1), blog.getRaw(2));
|
||||
return new Blog(g, blog.getString(0), description, a);
|
||||
// TODO change permanent depending on how this will be used
|
||||
boolean permanent = false;
|
||||
return new Blog(g, blog.getString(0), description, a, permanent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,22 +6,31 @@ import org.briarproject.api.blogs.BlogFactory;
|
||||
import org.briarproject.api.blogs.BlogManager;
|
||||
import org.briarproject.api.blogs.BlogPost;
|
||||
import org.briarproject.api.blogs.BlogPostHeader;
|
||||
import org.briarproject.api.clients.Client;
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
import org.briarproject.api.contact.Contact;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.data.BdfEntry;
|
||||
import org.briarproject.api.data.BdfList;
|
||||
import org.briarproject.api.data.MetadataParser;
|
||||
import org.briarproject.api.db.DatabaseComponent;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.db.Transaction;
|
||||
import org.briarproject.api.event.BlogPostAddedEvent;
|
||||
import org.briarproject.api.identity.Author;
|
||||
import org.briarproject.api.identity.Author.Status;
|
||||
import org.briarproject.api.identity.AuthorId;
|
||||
import org.briarproject.api.identity.IdentityManager;
|
||||
import org.briarproject.api.identity.IdentityManager.AddIdentityHook;
|
||||
import org.briarproject.api.identity.IdentityManager.RemoveIdentityHook;
|
||||
import org.briarproject.api.identity.LocalAuthor;
|
||||
import org.briarproject.api.sync.ClientId;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.clients.BdfIncomingMessageHook;
|
||||
import org.briarproject.util.StringUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -31,24 +40,29 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_ID;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_NAME;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_CONTENT_TYPE;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_DESCRIPTION;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_HAS_BODY;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_PARENT;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_PUBLIC_KEY;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_READ;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_TEASER;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_TIME_RECEIVED;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_TITLE;
|
||||
import static org.briarproject.api.contact.ContactManager.AddContactHook;
|
||||
import static org.briarproject.api.contact.ContactManager.RemoveContactHook;
|
||||
|
||||
class BlogManagerImpl implements BlogManager {
|
||||
class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
||||
AddContactHook, RemoveContactHook, Client,
|
||||
AddIdentityHook, RemoveIdentityHook {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BlogManagerImpl.class.getName());
|
||||
@@ -59,17 +73,19 @@ class BlogManagerImpl implements BlogManager {
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final IdentityManager identityManager;
|
||||
private final ClientHelper clientHelper;
|
||||
private final BlogFactory blogFactory;
|
||||
private final List<RemoveBlogHook> removeHooks;
|
||||
|
||||
@Inject
|
||||
BlogManagerImpl(DatabaseComponent db, IdentityManager identityManager,
|
||||
ClientHelper clientHelper, BlogFactory blogFactory) {
|
||||
ClientHelper clientHelper, MetadataParser metadataParser,
|
||||
BlogFactory blogFactory) {
|
||||
super(clientHelper, metadataParser);
|
||||
|
||||
this.db = db;
|
||||
this.identityManager = identityManager;
|
||||
this.clientHelper = clientHelper;
|
||||
this.blogFactory = blogFactory;
|
||||
removeHooks = new CopyOnWriteArrayList<RemoveBlogHook>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -77,6 +93,79 @@ class BlogManagerImpl implements BlogManager {
|
||||
return CLIENT_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createLocalState(Transaction txn) throws DbException {
|
||||
// Ensure every identity does have its own personal blog
|
||||
// TODO this can probably be removed once #446 is resolved and all users migrated to a new version
|
||||
for (LocalAuthor a : db.getLocalAuthors(txn)) {
|
||||
Blog b = blogFactory.createPersonalBlog(a);
|
||||
Group g = b.getGroup();
|
||||
if (!db.containsGroup(txn, g.getId())) {
|
||||
db.addGroup(txn, g);
|
||||
for (ContactId c : db.getContacts(txn, a.getId())) {
|
||||
db.setVisibleToContact(txn, c, g.getId(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ensure that we have the personal blogs of all pre-existing contacts
|
||||
for (Contact c : db.getContacts(txn)) addingContact(txn, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addingContact(Transaction txn, Contact c) throws DbException {
|
||||
// get personal blog of the contact
|
||||
Blog b = blogFactory.createPersonalBlog(c.getAuthor());
|
||||
Group g = b.getGroup();
|
||||
if (!db.containsGroup(txn, g.getId())) {
|
||||
// add the personal blog of the contact
|
||||
db.addGroup(txn, g);
|
||||
db.setVisibleToContact(txn, c.getId(), g.getId(), true);
|
||||
|
||||
// share our personal blog with the new contact
|
||||
LocalAuthor a = db.getLocalAuthor(txn, c.getLocalAuthorId());
|
||||
Blog b2 = blogFactory.createPersonalBlog(a);
|
||||
db.setVisibleToContact(txn, c.getId(), b2.getId(), true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removingContact(Transaction txn, Contact c) throws DbException {
|
||||
if (c != null) {
|
||||
Blog b = blogFactory.createPersonalBlog(c.getAuthor());
|
||||
db.removeGroup(txn, b.getGroup());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addingIdentity(Transaction txn, LocalAuthor a)
|
||||
throws DbException {
|
||||
|
||||
// add a personal blog for the new identity
|
||||
LOG.info("New Personal Blog Added.");
|
||||
Blog b = blogFactory.createPersonalBlog(a);
|
||||
db.addGroup(txn, b.getGroup());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removingIdentity(Transaction txn, LocalAuthor a)
|
||||
throws DbException {
|
||||
|
||||
// remove the personal blog of that identity
|
||||
Blog b = blogFactory.createPersonalBlog(a);
|
||||
db.removeGroup(txn, b.getGroup());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void incomingMessage(Transaction txn, Message m, BdfList list,
|
||||
BdfDictionary meta) throws DbException, FormatException {
|
||||
|
||||
GroupId groupId = m.getGroupId();
|
||||
BlogPostHeader h = getPostHeaderFromMetadata(txn, m.getId(), meta);
|
||||
BlogPostAddedEvent event =
|
||||
new BlogPostAddedEvent(groupId, h, false);
|
||||
txn.attach(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Blog addBlog(LocalAuthor localAuthor, String name,
|
||||
String description) throws DbException {
|
||||
@@ -101,13 +190,25 @@ class BlogManagerImpl implements BlogManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLocalPost(BlogPost p) throws DbException {
|
||||
public void removeBlog(Blog b) throws DbException {
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
for (RemoveBlogHook hook : removeHooks)
|
||||
hook.removingBlog(txn, b);
|
||||
db.removeGroup(txn, b.getGroup());
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLocalPost(BlogPost p) throws DbException {
|
||||
BdfDictionary meta;
|
||||
try {
|
||||
meta = new BdfDictionary();
|
||||
if (p.getTitle() != null) meta.put(KEY_TITLE, p.getTitle());
|
||||
meta.put(KEY_TEASER, p.getTeaser());
|
||||
meta.put(KEY_TIMESTAMP, p.getMessage().getTimestamp());
|
||||
meta.put(KEY_HAS_BODY, p.hasBody());
|
||||
if (p.getParent() != null) meta.put(KEY_PARENT, p.getParent());
|
||||
|
||||
Author a = p.getAuthor();
|
||||
@@ -123,6 +224,22 @@ class BlogManagerImpl implements BlogManager {
|
||||
} catch (FormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
// broadcast event about new post
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
GroupId groupId = p.getMessage().getGroupId();
|
||||
MessageId postId = p.getMessage().getId();
|
||||
BlogPostHeader h = getPostHeaderFromMetadata(txn, postId, meta);
|
||||
BlogPostAddedEvent event =
|
||||
new BlogPostAddedEvent(groupId, h, true);
|
||||
txn.attach(event);
|
||||
txn.setComplete();
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -163,6 +280,11 @@ class BlogManagerImpl implements BlogManager {
|
||||
return Collections.unmodifiableList(blogs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Blog getPersonalBlog(Author author) throws DbException {
|
||||
return blogFactory.createPersonalBlog(author);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Blog> getBlogs() throws DbException {
|
||||
try {
|
||||
@@ -189,16 +311,20 @@ class BlogManagerImpl implements BlogManager {
|
||||
@Nullable
|
||||
public byte[] getPostBody(MessageId m) throws DbException {
|
||||
try {
|
||||
// content, signature
|
||||
// content: parent, contentType, title, teaser, body, attachments
|
||||
BdfList message = clientHelper.getMessageAsList(m);
|
||||
BdfList content = message.getList(0);
|
||||
return content.getRaw(4);
|
||||
return getPostBody(message);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getPostBody(BdfList message) throws FormatException {
|
||||
// content, signature
|
||||
// content: parent, contentType, title, body, attachments
|
||||
BdfList content = message.getList(0);
|
||||
return content.getRaw(3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<BlogPostHeader> getPostHeaders(GroupId g)
|
||||
throws DbException {
|
||||
@@ -213,26 +339,9 @@ class BlogManagerImpl implements BlogManager {
|
||||
for (Entry<MessageId, BdfDictionary> entry : metadata.entrySet()) {
|
||||
try {
|
||||
BdfDictionary meta = entry.getValue();
|
||||
String title = meta.getOptionalString(KEY_TITLE);
|
||||
String teaser = meta.getString(KEY_TEASER);
|
||||
boolean hasBody = meta.getBoolean(KEY_HAS_BODY);
|
||||
long timestamp = meta.getLong(KEY_TIMESTAMP);
|
||||
MessageId parentId = null;
|
||||
if (meta.containsKey(KEY_PARENT))
|
||||
parentId = new MessageId(meta.getRaw(KEY_PARENT));
|
||||
|
||||
BdfDictionary d = meta.getDictionary(KEY_AUTHOR);
|
||||
AuthorId authorId = new AuthorId(d.getRaw(KEY_AUTHOR_ID));
|
||||
String name = d.getString(KEY_AUTHOR_NAME);
|
||||
byte[] publicKey = d.getRaw(KEY_PUBLIC_KEY);
|
||||
Author author = new Author(authorId, name, publicKey);
|
||||
Status authorStatus = identityManager.getAuthorStatus(authorId);
|
||||
|
||||
String contentType = meta.getString(KEY_CONTENT_TYPE);
|
||||
boolean read = meta.getBoolean(KEY_READ);
|
||||
headers.add(new BlogPostHeader(title, teaser, hasBody,
|
||||
entry.getKey(), parentId, timestamp, author,
|
||||
authorStatus, contentType, read));
|
||||
BlogPostHeader h =
|
||||
getPostHeaderFromMetadata(null, entry.getKey(), meta);
|
||||
headers.add(h);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
@@ -251,10 +360,43 @@ class BlogManagerImpl implements BlogManager {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerRemoveBlogHook(RemoveBlogHook hook) {
|
||||
removeHooks.add(hook);
|
||||
}
|
||||
|
||||
private String getBlogDescription(Transaction txn, GroupId g)
|
||||
throws DbException, FormatException {
|
||||
BdfDictionary d = clientHelper.getGroupMetadataAsDictionary(txn, g);
|
||||
return d.getString(KEY_DESCRIPTION);
|
||||
return d.getString(KEY_DESCRIPTION, "");
|
||||
}
|
||||
|
||||
private BlogPostHeader getPostHeaderFromMetadata(@Nullable Transaction txn,
|
||||
MessageId id, BdfDictionary meta)
|
||||
throws DbException, FormatException {
|
||||
|
||||
String title = meta.getOptionalString(KEY_TITLE);
|
||||
long timestamp = meta.getLong(KEY_TIMESTAMP);
|
||||
long timeReceived = meta.getLong(KEY_TIME_RECEIVED, timestamp);
|
||||
MessageId parentId = null;
|
||||
if (meta.containsKey(KEY_PARENT))
|
||||
parentId = new MessageId(meta.getRaw(KEY_PARENT));
|
||||
|
||||
BdfDictionary d = meta.getDictionary(KEY_AUTHOR);
|
||||
AuthorId authorId = new AuthorId(d.getRaw(KEY_AUTHOR_ID));
|
||||
String name = d.getString(KEY_AUTHOR_NAME);
|
||||
byte[] publicKey = d.getRaw(KEY_PUBLIC_KEY);
|
||||
Author author = new Author(authorId, name, publicKey);
|
||||
Status authorStatus;
|
||||
if (txn == null)
|
||||
authorStatus = identityManager.getAuthorStatus(authorId);
|
||||
else {
|
||||
authorStatus = identityManager.getAuthorStatus(txn, authorId);
|
||||
}
|
||||
|
||||
String contentType = meta.getString(KEY_CONTENT_TYPE);
|
||||
boolean read = meta.getBoolean(KEY_READ);
|
||||
return new BlogPostHeader(title, id, parentId, timestamp, timeReceived,
|
||||
author, authorStatus, contentType, read);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import java.security.GeneralSecurityException;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH;
|
||||
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_TEASER_LENGTH;
|
||||
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_TITLE_LENGTH;
|
||||
import static org.briarproject.api.blogs.BlogConstants.MAX_CONTENT_TYPE_LENGTH;
|
||||
|
||||
@@ -39,25 +38,22 @@ class BlogPostFactoryImpl implements BlogPostFactory {
|
||||
|
||||
@Override
|
||||
public BlogPost createBlogPost(@NotNull GroupId groupId,
|
||||
@Nullable String title, @NotNull String teaser, long timestamp,
|
||||
@Nullable String title, long timestamp,
|
||||
@Nullable MessageId parent, @NotNull LocalAuthor author,
|
||||
@NotNull String contentType, @Nullable byte[] body)
|
||||
@NotNull String contentType, @NotNull byte[] body)
|
||||
throws FormatException, GeneralSecurityException {
|
||||
|
||||
// Validate the arguments
|
||||
if (title != null &&
|
||||
StringUtils.toUtf8(title).length > MAX_BLOG_POST_TITLE_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
if (StringUtils.toUtf8(teaser).length > MAX_BLOG_POST_TEASER_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
if (StringUtils.toUtf8(contentType).length > MAX_CONTENT_TYPE_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
if (body != null && body.length > MAX_BLOG_POST_BODY_LENGTH)
|
||||
if (body.length > MAX_BLOG_POST_BODY_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
// Serialise the data to be signed
|
||||
BdfList content =
|
||||
BdfList.of(parent, contentType, title, teaser, body, null);
|
||||
BdfList content = BdfList.of(parent, contentType, title, body, null);
|
||||
BdfList signed = BdfList.of(groupId, timestamp, content);
|
||||
|
||||
// Generate the signature
|
||||
@@ -72,7 +68,6 @@ class BlogPostFactoryImpl implements BlogPostFactory {
|
||||
// Serialise the signed message
|
||||
BdfList message = BdfList.of(content, sig);
|
||||
Message m = clientHelper.createMessage(groupId, timestamp, message);
|
||||
return new BlogPost(title, teaser, body != null, m, parent, author,
|
||||
contentType);
|
||||
return new BlogPost(title, m, parent, author, contentType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.briarproject.api.crypto.KeyParser;
|
||||
import org.briarproject.api.crypto.PublicKey;
|
||||
import org.briarproject.api.crypto.Signature;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.data.BdfEntry;
|
||||
import org.briarproject.api.data.BdfList;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
import org.briarproject.api.identity.Author;
|
||||
@@ -25,15 +26,17 @@ import java.security.GeneralSecurityException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_ID;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_NAME;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_CONTENT_TYPE;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_HAS_BODY;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_PARENT;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_PUBLIC_KEY;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_READ;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_TEASER;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_TIME_RECEIVED;
|
||||
import static org.briarproject.api.blogs.BlogConstants.KEY_TITLE;
|
||||
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH;
|
||||
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_TEASER_LENGTH;
|
||||
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_TITLE_LENGTH;
|
||||
import static org.briarproject.api.blogs.BlogConstants.MAX_CONTENT_TYPE_LENGTH;
|
||||
import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||
@@ -60,37 +63,36 @@ class BlogPostValidator extends BdfMessageValidator {
|
||||
checkSize(body, 2);
|
||||
BdfList content = body.getList(0);
|
||||
|
||||
// Content: Parent ID, content type, title (optional), teaser,
|
||||
// post body (optional), attachments (optional)
|
||||
checkSize(body, 6);
|
||||
// Content: Parent ID, content type, title (optional), post body,
|
||||
// attachments (optional)
|
||||
checkSize(content, 5);
|
||||
// Parent ID is optional
|
||||
byte[] parent = content.getOptionalRaw(0);
|
||||
checkLength(parent, UniqueId.LENGTH);
|
||||
// Content type
|
||||
String contentType = content.getString(1);
|
||||
checkLength(contentType, 0, MAX_CONTENT_TYPE_LENGTH);
|
||||
if (!contentType.equals("text/plain"))
|
||||
throw new InvalidMessageException("Invalid content type");
|
||||
// Blog post title is optional
|
||||
String title = content.getOptionalString(2);
|
||||
checkLength(contentType, 0, MAX_BLOG_POST_TITLE_LENGTH);
|
||||
// Blog teaser
|
||||
String teaser = content.getString(3);
|
||||
// TODO make sure that there is only text in the teaser
|
||||
checkLength(contentType, 0, MAX_BLOG_POST_TEASER_LENGTH);
|
||||
// Blog post body is optional
|
||||
byte[] postBody = content.getOptionalRaw(4);
|
||||
// Blog post body
|
||||
byte[] postBody = content.getRaw(3);
|
||||
checkLength(postBody, 0, MAX_BLOG_POST_BODY_LENGTH);
|
||||
// Attachments
|
||||
BdfDictionary attachments = content.getOptionalDictionary(5);
|
||||
BdfDictionary attachments = content.getOptionalDictionary(4);
|
||||
// TODO handle attachments somehow
|
||||
|
||||
// Signature
|
||||
byte[] sig = body.getRaw(1);
|
||||
checkLength(sig, 0, MAX_SIGNATURE_LENGTH);
|
||||
// Verify the signature
|
||||
Author a;
|
||||
try {
|
||||
// Get the blog author
|
||||
Blog b = blogFactory.parseBlog(g, ""); // description doesn't matter
|
||||
Author a = b.getAuthor();
|
||||
a = b.getAuthor();
|
||||
// Parse the public key
|
||||
KeyParser keyParser = crypto.getSignatureKeyParser();
|
||||
PublicKey key = keyParser.parsePublicKey(a.getPublicKey());
|
||||
@@ -111,9 +113,14 @@ class BlogPostValidator extends BdfMessageValidator {
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
Collection<MessageId> dependencies = null;
|
||||
if (title != null) meta.put(KEY_TITLE, title);
|
||||
meta.put(KEY_TEASER, teaser);
|
||||
meta.put(KEY_HAS_BODY, postBody != null);
|
||||
BdfDictionary author = BdfDictionary.of(
|
||||
new BdfEntry(KEY_AUTHOR_ID, a.getId()),
|
||||
new BdfEntry(KEY_AUTHOR_NAME, a.getName()),
|
||||
new BdfEntry(KEY_PUBLIC_KEY, a.getPublicKey())
|
||||
);
|
||||
meta.put(KEY_AUTHOR, author);
|
||||
meta.put(KEY_TIMESTAMP, m.getTimestamp());
|
||||
meta.put(KEY_TIME_RECEIVED, clock.currentTimeMillis());
|
||||
if (parent != null) {
|
||||
meta.put(KEY_PARENT, parent);
|
||||
dependencies = Collections.singletonList(new MessageId(parent));
|
||||
|
||||
@@ -4,11 +4,12 @@ import org.briarproject.api.blogs.BlogFactory;
|
||||
import org.briarproject.api.blogs.BlogManager;
|
||||
import org.briarproject.api.blogs.BlogPostFactory;
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
import org.briarproject.api.contact.ContactManager;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
import org.briarproject.api.db.DatabaseComponent;
|
||||
import org.briarproject.api.identity.AuthorFactory;
|
||||
import org.briarproject.api.identity.IdentityManager;
|
||||
import org.briarproject.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.api.sync.GroupFactory;
|
||||
import org.briarproject.api.sync.ValidationManager;
|
||||
import org.briarproject.api.system.Clock;
|
||||
@@ -19,17 +20,31 @@ import javax.inject.Singleton;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import static org.briarproject.blogs.BlogManagerImpl.CLIENT_ID;
|
||||
|
||||
@Module
|
||||
public class BlogsModule {
|
||||
|
||||
public static class EagerSingletons {
|
||||
@Inject
|
||||
BlogPostValidator blogPostValidator;
|
||||
@Inject
|
||||
BlogManager blogManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
BlogManager provideBlogManager(BlogManagerImpl blogManager) {
|
||||
BlogManager provideBlogManager(BlogManagerImpl blogManager,
|
||||
LifecycleManager lifecycleManager, ContactManager contactManager,
|
||||
IdentityManager identityManager,
|
||||
ValidationManager validationManager) {
|
||||
|
||||
lifecycleManager.registerClient(blogManager);
|
||||
contactManager.registerAddContactHook(blogManager);
|
||||
contactManager.registerRemoveContactHook(blogManager);
|
||||
identityManager.registerAddIdentityHook(blogManager);
|
||||
identityManager.registerRemoveIdentityHook(blogManager);
|
||||
validationManager.registerIncomingMessageHook(CLIENT_ID, blogManager);
|
||||
return blogManager;
|
||||
}
|
||||
|
||||
@@ -54,8 +69,7 @@ public class BlogsModule {
|
||||
|
||||
BlogPostValidator validator = new BlogPostValidator(crypto,
|
||||
blogFactory, clientHelper, metadataEncoder, clock);
|
||||
validationManager.registerMessageValidator(
|
||||
BlogManagerImpl.CLIENT_ID, validator);
|
||||
validationManager.registerMessageValidator(CLIENT_ID, validator);
|
||||
|
||||
return validator;
|
||||
}
|
||||
|
||||
@@ -99,17 +99,24 @@ class IdentityManagerImpl implements IdentityManager {
|
||||
public Status getAuthorStatus(AuthorId authorId) throws DbException {
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
// Compare to the IDs of the user's identities
|
||||
for (LocalAuthor a : db.getLocalAuthors(txn))
|
||||
if (a.getId().equals(authorId)) return VERIFIED;
|
||||
// Compare to the IDs of contacts' identities
|
||||
for (Contact c : db.getContacts(txn))
|
||||
if (c.getAuthor().getId().equals(authorId)) return VERIFIED;
|
||||
|
||||
// TODO also handle UNVERIFIED when #261 is implemented
|
||||
return UNKNOWN;
|
||||
return getAuthorStatus(txn, authorId);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Status getAuthorStatus(Transaction txn, AuthorId authorId)
|
||||
throws DbException {
|
||||
// Compare to the IDs of the user's identities
|
||||
for (LocalAuthor a : db.getLocalAuthors(txn))
|
||||
if (a.getId().equals(authorId)) return VERIFIED;
|
||||
// Compare to the IDs of contacts' identities
|
||||
for (Contact c : db.getContacts(txn))
|
||||
if (c.getAuthor().getId().equals(authorId)) return VERIFIED;
|
||||
|
||||
// TODO also handle UNVERIFIED when #261 is implemented
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.briarproject.api.blogs.Blog;
|
||||
import org.briarproject.api.blogs.BlogFactory;
|
||||
import org.briarproject.api.blogs.BlogInvitationMessage;
|
||||
import org.briarproject.api.blogs.BlogManager;
|
||||
import org.briarproject.api.blogs.BlogManager.RemoveBlogHook;
|
||||
import org.briarproject.api.blogs.BlogSharingManager;
|
||||
import org.briarproject.api.blogs.BlogSharingMessage.BlogInvitation;
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
@@ -40,7 +41,7 @@ import static org.briarproject.api.blogs.BlogConstants.BLOG_TITLE;
|
||||
|
||||
class BlogSharingManagerImpl extends
|
||||
SharingManagerImpl<Blog, BlogInvitation, BlogInvitationMessage, BlogInviteeSessionState, BlogSharerSessionState, BlogInvitationReceivedEvent, BlogInvitationResponseReceivedEvent>
|
||||
implements BlogSharingManager {
|
||||
implements BlogSharingManager, RemoveBlogHook {
|
||||
|
||||
static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString(
|
||||
"bee438b5de0b3a685badc4e49d76e72d"
|
||||
@@ -124,6 +125,11 @@ class BlogSharingManagerImpl extends
|
||||
return irrFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removingBlog(Transaction txn, Blog b) throws DbException {
|
||||
removingShareable(txn, b);
|
||||
}
|
||||
|
||||
static class SFactory implements
|
||||
ShareableFactory<Blog, BlogInvitation, BlogInviteeSessionState, BlogSharerSessionState> {
|
||||
|
||||
|
||||
@@ -792,6 +792,8 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IM
|
||||
} else if (task == TASK_REMOVE_SHAREABLE_FROM_LIST_SHARED_WITH_US) {
|
||||
removeFromList(txn, groupId, SHARED_WITH_US, f);
|
||||
} else if (task == TASK_ADD_SHARED_SHAREABLE) {
|
||||
// TODO we might want to call the add() method of the respective
|
||||
// manager here, because blogs add a description for example
|
||||
db.addGroup(txn, f.getGroup());
|
||||
db.setVisibleToContact(txn, contactId, f.getId(), true);
|
||||
} else if (task == TASK_ADD_SHAREABLE_TO_LIST_TO_BE_SHARED_BY_US) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.sharing;
|
||||
|
||||
import org.briarproject.api.blogs.BlogManager;
|
||||
import org.briarproject.api.blogs.BlogSharingManager;
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
import org.briarproject.api.clients.MessageQueueManager;
|
||||
@@ -49,6 +50,7 @@ public class SharingModule {
|
||||
LifecycleManager lifecycleManager,
|
||||
ContactManager contactManager,
|
||||
MessageQueueManager messageQueueManager,
|
||||
BlogManager blogManager,
|
||||
BlogSharingManagerImpl blogSharingManager) {
|
||||
|
||||
lifecycleManager.registerClient(blogSharingManager);
|
||||
@@ -56,6 +58,7 @@ public class SharingModule {
|
||||
contactManager.registerRemoveContactHook(blogSharingManager);
|
||||
messageQueueManager.registerIncomingMessageHook(
|
||||
BlogSharingManagerImpl.CLIENT_ID, blogSharingManager);
|
||||
blogManager.registerRemoveBlogHook(blogSharingManager);
|
||||
|
||||
return blogSharingManager;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user