diff --git a/briar-api/src/org/briarproject/api/blogs/Blog.java b/briar-api/src/org/briarproject/api/blogs/Blog.java index c97e42be2aae72185845fa2b5ea05ee257ebd17c..4e6827891a67ebde4d63d842fa7e0e5bce2c1269 100644 --- a/briar-api/src/org/briarproject/api/blogs/Blog.java +++ b/briar-api/src/org/briarproject/api/blogs/Blog.java @@ -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; + } } diff --git a/briar-api/src/org/briarproject/api/blogs/BlogConstants.java b/briar-api/src/org/briarproject/api/blogs/BlogConstants.java index 738e012a3ef2872f1c341aacc7e5a6a68d5a7da5..7bbaacdb8b2c33bd74c86ee1e5ce0a103479514c 100644 --- a/briar-api/src/org/briarproject/api/blogs/BlogConstants.java +++ b/briar-api/src/org/briarproject/api/blogs/BlogConstants.java @@ -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"; diff --git a/briar-api/src/org/briarproject/api/blogs/BlogFactory.java b/briar-api/src/org/briarproject/api/blogs/BlogFactory.java index 8da9db1926f11d4dc31ed633fb61d794aa1444fb..190236bae1f5ae92efc79f535fa1c36e4a02c0e3 100644 --- a/briar-api/src/org/briarproject/api/blogs/BlogFactory.java +++ b/briar-api/src/org/briarproject/api/blogs/BlogFactory.java @@ -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; diff --git a/briar-api/src/org/briarproject/api/blogs/BlogManager.java b/briar-api/src/org/briarproject/api/blogs/BlogManager.java index e01fa5efb9a8bc30cf6dbd4dd3f97d063dae713c..7d6188feda24f7c3fdaf06b28460c0878c467676 100644 --- a/briar-api/src/org/briarproject/api/blogs/BlogManager.java +++ b/briar-api/src/org/briarproject/api/blogs/BlogManager.java @@ -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; + } + } diff --git a/briar-api/src/org/briarproject/api/blogs/BlogPost.java b/briar-api/src/org/briarproject/api/blogs/BlogPost.java index a6aa1167aebc9c24ac00bfc81fa1f5ffc157a064..3854c047279b90f2718a39644cee381893ad9a5b 100644 --- a/briar-api/src/org/briarproject/api/blogs/BlogPost.java +++ b/briar-api/src/org/briarproject/api/blogs/BlogPost.java @@ -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; - } } diff --git a/briar-api/src/org/briarproject/api/blogs/BlogPostFactory.java b/briar-api/src/org/briarproject/api/blogs/BlogPostFactory.java index 2565bfb0e731dc532e8cdc0a691538f8c8166d3a..6ced9ba20fe646822eced9d1269648f0740e51f2 100644 --- a/briar-api/src/org/briarproject/api/blogs/BlogPostFactory.java +++ b/briar-api/src/org/briarproject/api/blogs/BlogPostFactory.java @@ -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; } diff --git a/briar-api/src/org/briarproject/api/blogs/BlogPostHeader.java b/briar-api/src/org/briarproject/api/blogs/BlogPostHeader.java index 24d421c13959ddb173c91130f70f42bad408bc07..980ea27eb4005139004322a3a290dd0f888171dd 100644 --- a/briar-api/src/org/briarproject/api/blogs/BlogPostHeader.java +++ b/briar-api/src/org/briarproject/api/blogs/BlogPostHeader.java @@ -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; - } } diff --git a/briar-api/src/org/briarproject/api/event/BlogPostAddedEvent.java b/briar-api/src/org/briarproject/api/event/BlogPostAddedEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..c00e4efbff7e80d117cbcb2787a366a0a5988f50 --- /dev/null +++ b/briar-api/src/org/briarproject/api/event/BlogPostAddedEvent.java @@ -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; + } +} diff --git a/briar-api/src/org/briarproject/api/identity/IdentityManager.java b/briar-api/src/org/briarproject/api/identity/IdentityManager.java index e5420a043f267c9a643fd24987369552839e560e..e89f49d8ac0ea279093234cdcfeba0e4a3113fc4 100644 --- a/briar-api/src/org/briarproject/api/identity/IdentityManager.java +++ b/briar-api/src/org/briarproject/api/identity/IdentityManager.java @@ -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; } diff --git a/briar-core/src/org/briarproject/blogs/BlogFactoryImpl.java b/briar-core/src/org/briarproject/blogs/BlogFactoryImpl.java index b04401f5d1297854af0e45c6694afa5f26edc796..9a3e0583aa1f04952920139ea626f43d44e72de3 100644 --- a/briar-core/src/org/briarproject/blogs/BlogFactoryImpl.java +++ b/briar-core/src/org/briarproject/blogs/BlogFactoryImpl.java @@ -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); } } diff --git a/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java b/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java index eabfc251e3c7284557952ca8766196646cb063a9..182ea11869bb779e697910b5c9c73f5230ad75e9 100644 --- a/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java +++ b/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java @@ -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 { @@ -100,14 +189,26 @@ class BlogManagerImpl implements BlogManager { return b; } + @Override + public void removeBlog(Blog b) throws DbException { + Transaction txn = db.startTransaction(false); + try { + 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 { - BdfDictionary meta = new BdfDictionary(); + 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); + } } diff --git a/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java b/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java index e9ced2d3fe86ec555eb19e580e4c8ec5a83cdbcc..ce134d33c6214368f592037a545a605266f1ec88 100644 --- a/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java +++ b/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java @@ -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); } } diff --git a/briar-core/src/org/briarproject/blogs/BlogPostValidator.java b/briar-core/src/org/briarproject/blogs/BlogPostValidator.java index bacf02dff64d45e59ca80113158cba6e2778c206..d412f10aea4ebd7f5bf4948f213b59abce090cea 100644 --- a/briar-core/src/org/briarproject/blogs/BlogPostValidator.java +++ b/briar-core/src/org/briarproject/blogs/BlogPostValidator.java @@ -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)); diff --git a/briar-core/src/org/briarproject/blogs/BlogsModule.java b/briar-core/src/org/briarproject/blogs/BlogsModule.java index 4cf7894d1b608b2557a8098f5509a1ebb6ec7b18..055328ad9cbb664f7e027bb6bb46ce70a5309f8b 100644 --- a/briar-core/src/org/briarproject/blogs/BlogsModule.java +++ b/briar-core/src/org/briarproject/blogs/BlogsModule.java @@ -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; } diff --git a/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java b/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java index e04e206020edd797cf1da082d7b075df9a7156c3..5b1434ad1e3644b6964bea20916d5508d4ee53dc 100644 --- a/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java +++ b/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java @@ -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; + } + } diff --git a/briar-core/src/org/briarproject/sharing/BlogSharingManagerImpl.java b/briar-core/src/org/briarproject/sharing/BlogSharingManagerImpl.java index ba67ff635ee898c342994773cf8f5b4dd9787160..a25520fac0f49ffdcea490d227340bff041181cb 100644 --- a/briar-core/src/org/briarproject/sharing/BlogSharingManagerImpl.java +++ b/briar-core/src/org/briarproject/sharing/BlogSharingManagerImpl.java @@ -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> { diff --git a/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java b/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java index 0d55b4a3cef39ef2fdb61609485968f343975c03..e4929fb7948d7a9d492782b15bd35223875c6896 100644 --- a/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java +++ b/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java @@ -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) { diff --git a/briar-core/src/org/briarproject/sharing/SharingModule.java b/briar-core/src/org/briarproject/sharing/SharingModule.java index d62c7dccf7945810b7d271e85c4ac43e78131268..3af07e12925533cfe19898dd00c22178bf23b2ab 100644 --- a/briar-core/src/org/briarproject/sharing/SharingModule.java +++ b/briar-core/src/org/briarproject/sharing/SharingModule.java @@ -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; }