+ public void bookmarkPost(Post post) {
+ database.bookmarkPost(post);
+ }
+
+ /**
+ * Removes the given post from the bookmarks.
+ *
+ * @param post
+ * The post to unbookmark
+ */
+ public void unbookmarkPost(Post post) {
+ database.unbookmarkPost(post);
+ }
+
+ /**
+ * Creates a new reply.
+ *
+ * @param sone
+ * The Sone that creates the reply
+ * @param post
+ * The post that this reply refers to
+ * @param text
+ * The text of the reply
+ * @return The created reply
+ */
+ public PostReply createReply(Sone sone, Post post, String text) {
+ checkNotNull(text, "text must not be null");
+ checkArgument(text.trim().length() > 0, "text must not be empty");
+ if (!sone.isLocal()) {
+ logger.log(Level.FINE, String.format("Tried to create reply for non-local Sone: %s", sone));
+ return null;
+ }
+ PostReplyBuilder postReplyBuilder = postReplyBuilder();
+ postReplyBuilder.randomId().from(sone.getId()).to(post.getId()).currentTime().withText(text.trim());
+ final PostReply reply = postReplyBuilder.build();
+ database.storePostReply(reply);
+ eventBus.post(new NewPostReplyFoundEvent(reply));
+ sone.addReply(reply);
+ touchConfiguration();
+ localElementTicker.schedule(new MarkReplyKnown(reply), 10, TimeUnit.SECONDS);
+ return reply;
+ }
+
+ /**
+ * Deletes the given reply.
+ *
+ * @param reply
+ * The reply to delete
+ */
+ public void deleteReply(PostReply reply) {
+ Sone sone = reply.getSone();
+ if (!sone.isLocal()) {
+ logger.log(Level.FINE, String.format("Tried to delete non-local reply: %s", reply));
+ return;
+ }
+ database.removePostReply(reply);
+ markReplyKnown(reply);
+ sone.removeReply(reply);
+ touchConfiguration();
+ }
+
+ /**
+ * Marks the given reply as known, if it is currently not a known reply
+ * (according to {@link Reply#isKnown()}).
+ *
+ * @param reply
+ * The reply to mark as known
+ */
+ public void markReplyKnown(PostReply reply) {
+ boolean previouslyKnown = reply.isKnown();
+ reply.setKnown(true);
+ eventBus.post(new MarkPostReplyKnownEvent(reply));
+ if (!previouslyKnown) {
+ touchConfiguration();
+ }
+ }
+
+ /**
+ * Creates a new album for the given Sone.
+ *
+ * @param sone
+ * The Sone to create the album for
+ * @param parent
+ * The parent of the album (may be {@code null} to create a
+ * top-level album)
+ * @return The new album
+ */
+ public Album createAlbum(Sone sone, Album parent) {
+ Album album = database.newAlbumBuilder().randomId().by(sone).build();
+ database.storeAlbum(album);
+ parent.addAlbum(album);
+ return album;
+ }
+
+ /**
+ * Deletes the given album. The owner of the album has to be a local Sone,
+ * and the album has to be {@link Album#isEmpty() empty} to be deleted.
+ *
+ * @param album
+ * The album to remove
+ */
+ public void deleteAlbum(Album album) {
+ checkNotNull(album, "album must not be null");
+ checkArgument(album.getSone().isLocal(), "album’s Sone must be a local Sone");
+ if (!album.isEmpty()) {
+ return;
+ }
+ album.getParent().removeAlbum(album);
+ database.removeAlbum(album);
+ touchConfiguration();
+ }
+
+ /**
+ * Creates a new image.
+ *
+ * @param sone
+ * The Sone creating the image
+ * @param album
+ * The album the image will be inserted into
+ * @param temporaryImage
+ * The temporary image to create the image from
+ * @return The newly created image
+ */
+ public Image createImage(Sone sone, Album album, TemporaryImage temporaryImage) {
+ checkNotNull(sone, "sone must not be null");
+ checkNotNull(album, "album must not be null");
+ checkNotNull(temporaryImage, "temporaryImage must not be null");
+ checkArgument(sone.isLocal(), "sone must be a local Sone");
+ checkArgument(sone.equals(album.getSone()), "album must belong to the given Sone");
+ Image image = database.newImageBuilder().withId(temporaryImage.getId()).build().modify().setSone(sone).setCreationTime(System.currentTimeMillis()).update();
+ album.addImage(image);
+ database.storeImage(image);
+ imageInserter.insertImage(temporaryImage, image);
+ return image;
+ }
+
+ /**
+ * Deletes the given image. This method will also delete a matching
+ * temporary image.
+ *
+ * @see #deleteTemporaryImage(String)
+ * @param image
+ * The image to delete
+ */
+ public void deleteImage(Image image) {
+ checkNotNull(image, "image must not be null");
+ checkArgument(image.getSone().isLocal(), "image must belong to a local Sone");
+ deleteTemporaryImage(image.getId());
+ image.getAlbum().removeImage(image);
+ database.removeImage(image);
+ touchConfiguration();
+ }
+
+ /**
+ * Creates a new temporary image.
+ *
+ * @param mimeType
+ * The MIME type of the temporary image
+ * @param imageData
+ * The encoded data of the image
+ * @return The temporary image
+ */
+ public TemporaryImage createTemporaryImage(String mimeType, byte[] imageData) {
+ TemporaryImage temporaryImage = new TemporaryImage();
+ temporaryImage.setMimeType(mimeType).setImageData(imageData);
+ synchronized (temporaryImages) {
+ temporaryImages.put(temporaryImage.getId(), temporaryImage);
+ }
+ return temporaryImage;
+ }
+
+ /**
+ * Deletes the temporary image with the given ID.
+ *
+ * @param imageId
+ * The ID of the temporary image to delete
+ */
+ public void deleteTemporaryImage(String imageId) {
+ checkNotNull(imageId, "imageId must not be null");
+ synchronized (temporaryImages) {
+ temporaryImages.remove(imageId);
+ }
+ Image image = getImage(imageId, false);
+ if (image != null) {
+ imageInserter.cancelImageInsert(image);
+ }
+ }
+
+ /**
+ * Notifies the core that the configuration, either of the core or of a
+ * single local Sone, has changed, and that the configuration should be
+ * saved.
+ */
+ public void touchConfiguration() {
+ lastConfigurationUpdate = System.currentTimeMillis();