Add image builder that creates “old” images.
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Thu, 10 Oct 2013 11:06:11 +0000 (13:06 +0200)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Thu, 10 Oct 2013 11:07:50 +0000 (13:07 +0200)
16 files changed:
src/main/java/net/pterodactylus/sone/core/Core.java
src/main/java/net/pterodactylus/sone/core/SoneDownloader.java
src/main/java/net/pterodactylus/sone/data/Image.java
src/main/java/net/pterodactylus/sone/data/ImageImpl.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/data/impl/AbstractImageBuilder.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/data/impl/ImageBuilderImpl.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/Database.java
src/main/java/net/pterodactylus/sone/database/ImageBuilder.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/ImageBuilderFactory.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/ImageDatabase.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/ImageProvider.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/ImageStore.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java
src/main/java/net/pterodactylus/sone/web/EditImagePage.java
src/main/java/net/pterodactylus/sone/web/UploadImagePage.java
src/main/java/net/pterodactylus/sone/web/ajax/EditImageAjaxPage.java

index 1ec4d7b..0b9b08f 100644 (file)
@@ -187,9 +187,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
        /** Trusted identities, sorted by own identities. */
        private final Multimap<OwnIdentity, Identity> trustedIdentities = Multimaps.synchronizedSetMultimap(HashMultimap.<OwnIdentity, Identity>create());
 
-       /** All known images. */
-       private final Map<String, Image> images = new HashMap<String, Image>();
-
        /** All temporary images. */
        private final Map<String, TemporaryImage> temporaryImages = new HashMap<String, TemporaryImage>();
 
@@ -669,14 +666,16 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         *         none was created
         */
        public Image getImage(String imageId, boolean create) {
-               synchronized (images) {
-                       Image image = images.get(imageId);
-                       if (create && (image == null)) {
-                               image = new Image(imageId);
-                               images.put(imageId, image);
-                       }
-                       return image;
+               Optional<Image> image = database.getImage(imageId);
+               if (image.isPresent()) {
+                       return image.get();
                }
+               if (!create) {
+                       return null;
+               }
+               Image newImage = database.newImageBuilder().withId(imageId).build();
+               database.storeImage(newImage);
+               return newImage;
        }
 
        /**
@@ -1032,18 +1031,16 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                                }
                        }
                        database.storePostReplies(sone, sone.getReplies());
-                       synchronized (images) {
-                               for (Album album : storedSone.get().getRootAlbum().getAlbums()) {
-                                       database.removeAlbum(album);
-                                       for (Image image : album.getImages()) {
-                                               images.remove(image.getId());
-                                       }
+                       for (Album album : storedSone.get().getRootAlbum().getAlbums()) {
+                               database.removeAlbum(album);
+                               for (Image image : album.getImages()) {
+                                       database.removeImage(image);
                                }
-                               for (Album album : sone.getRootAlbum().getAlbums()) {
-                                       database.storeAlbum(album);
-                                       for (Image image : album.getImages()) {
-                                               images.put(image.getId(), image);
-                                       }
+                       }
+                       for (Album album : sone.getRootAlbum().getAlbums()) {
+                               database.storeAlbum(album);
+                               for (Image image : album.getImages()) {
+                                       database.storeImage(image);
                                }
                        }
                        synchronized (sones) {
@@ -1286,8 +1283,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                                logger.log(Level.WARNING, "Invalid album image encountered, aborting load!");
                                return;
                        }
-                       Image image = getImage(imageId).setSone(sone).setCreationTime(creationTime).setKey(key);
-                       image.setTitle(title).setDescription(description).setWidth(width).setHeight(height);
+                       Image image = getImage(imageId).modify().setSone(sone).setCreationTime(creationTime).setKey(key).setTitle(title).setDescription(description).setWidth(width).setHeight(height).update();
                        album.addImage(image);
                }
 
@@ -1642,11 +1638,9 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                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 = new Image(temporaryImage.getId()).setSone(sone).setCreationTime(System.currentTimeMillis());
+               Image image = database.newImageBuilder().withId(temporaryImage.getId()).build().modify().setSone(sone).setCreationTime(System.currentTimeMillis()).update();
                album.addImage(image);
-               synchronized (images) {
-                       images.put(image.getId(), image);
-               }
+               database.storeImage(image);
                imageInserter.insertImage(temporaryImage, image);
                return image;
        }
@@ -1664,9 +1658,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                checkArgument(image.getSone().isLocal(), "image must belong to a local Sone");
                deleteTemporaryImage(image.getId());
                image.getAlbum().removeImage(image);
-               synchronized (images) {
-                       images.remove(image.getId());
-               }
+               database.removeImage(image);
                touchConfiguration();
        }
 
@@ -2223,7 +2215,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
        @Subscribe
        public void imageInsertFinished(ImageInsertFinishedEvent imageInsertFinishedEvent) {
                logger.log(Level.WARNING, String.format("Image insert finished for %s: %s", imageInsertFinishedEvent.image(), imageInsertFinishedEvent.resultingUri()));
-               imageInsertFinishedEvent.image().setKey(imageInsertFinishedEvent.resultingUri().toString());
+               imageInsertFinishedEvent.image().modify().setKey(imageInsertFinishedEvent.resultingUri().toString()).update();
                deleteTemporaryImage(imageInsertFinishedEvent.image().getId());
                touchConfiguration();
        }
index eaf56a9..56ae917 100644 (file)
@@ -495,9 +495,9 @@ public class SoneDownloader extends AbstractService {
                                                        logger.log(Level.WARNING, String.format("Downloaded Sone %s contains image %s with invalid dimensions (%s, %s)!", sone, imageId, imageWidthString, imageHeightString));
                                                        return null;
                                                }
-                                               Image image = core.getImage(imageId).setSone(sone).setKey(imageKey).setCreationTime(creationTime);
-                                               image.setTitle(imageTitle).setDescription(imageDescription);
-                                               image.setWidth(imageWidth).setHeight(imageHeight);
+                                               Image image = core.getImage(imageId).modify().setSone(sone).setKey(imageKey).setCreationTime(creationTime).update();
+                                               image = image.modify().setTitle(imageTitle).setDescription(imageDescription).update();
+                                               image = image.modify().setWidth(imageWidth).setHeight(imageHeight).update();
                                                album.addImage(image);
                                        }
                                }
index d8a8ab1..e3e1b4d 100644 (file)
 
 package net.pterodactylus.sone.data;
 
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-
-import java.util.UUID;
-
-import com.google.common.hash.Hasher;
-import com.google.common.hash.Hashing;
-
 /**
  * Container for image metadata.
  *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ * @author <a href="mailto:d.roden@xplosion.de">David Roden</a>
  */
-public class Image implements Identified, Fingerprintable {
-
-       /** The ID of the image. */
-       private final String id;
-
-       /** The Sone the image belongs to. */
-       private Sone sone;
-
-       /** The album this image belongs to. */
-       private Album album;
-
-       /** The request key of the image. */
-       private String key;
-
-       /** The creation time of the image. */
-       private long creationTime;
-
-       /** The width of the image. */
-       private int width;
-
-       /** The height of the image. */
-       private int height;
-
-       /** The title of the image. */
-       private String title;
-
-       /** The description of the image. */
-       private String description;
-
-       /**
-        * Creates a new image with a random ID.
-        */
-       public Image() {
-               this(UUID.randomUUID().toString());
-               setCreationTime(System.currentTimeMillis());
-       }
-
-       /**
-        * Creates a new image.
-        *
-        * @param id
-        *            The ID of the image
-        */
-       public Image(String id) {
-               this.id = checkNotNull(id, "id must not be null");
-       }
-
-       //
-       // ACCESSORS
-       //
+public interface Image extends Identified, Fingerprintable {
 
        /**
         * Returns the ID of this image.
         *
         * @return The ID of this image
         */
-       public String getId() {
-               return id;
-       }
+       String getId();
 
        /**
         * Returns the Sone this image belongs to.
         *
         * @return The Sone this image belongs to
         */
-       public Sone getSone() {
-               return sone;
-       }
-
-       /**
-        * Sets the owner of this image. The owner can only be set if no owner has
-        * yet been set.
-        *
-        * @param sone
-        *            The new owner of this image
-        * @return This image
-        */
-       public Image setSone(Sone sone) {
-               checkNotNull(sone, "sone must not be null");
-               checkArgument((this.sone == null) || this.sone.equals(sone), "sone must not already be set to another sone");
-               this.sone = sone;
-               return this;
-       }
+       Sone getSone();
 
        /**
         * Returns the album this image belongs to.
         *
         * @return The album this image belongs to
         */
-       public Album getAlbum() {
-               return album;
-       }
+       Album getAlbum();
 
        /**
         * Sets the album this image belongs to. The album of an image can only be
@@ -132,36 +53,14 @@ public class Image implements Identified, Fingerprintable {
         *            The album this image belongs to
         * @return This image
         */
-       public Image setAlbum(Album album) {
-               checkNotNull(album, "album must not be null");
-               checkNotNull(album.getSone().equals(getSone()), "album must belong to the same Sone as this image");
-               this.album = album;
-               return this;
-       }
+       Image setAlbum(Album album);
 
        /**
         * Returns the request key of this image.
         *
         * @return The request key of this image
         */
-       public String getKey() {
-               return key;
-       }
-
-       /**
-        * Sets the request key of this image. The request key can only be set as
-        * long as no request key has yet been set.
-        *
-        * @param key
-        *            The new request key of this image
-        * @return This image
-        */
-       public Image setKey(String key) {
-               checkNotNull(key, "key must not be null");
-               checkState((this.key == null) || this.key.equals(key), "key must not be already set to another key");
-               this.key = key;
-               return this;
-       }
+       String getKey();
 
        /**
         * Returns whether the image has already been inserted. An image is
@@ -171,9 +70,7 @@ public class Image implements Identified, Fingerprintable {
         * @return {@code true} if there is a key for this image, {@code false}
         *         otherwise
         */
-       public boolean isInserted() {
-               return key != null;
-       }
+       boolean isInserted();
 
        /**
         * Returns the creation time of this image.
@@ -181,154 +78,62 @@ public class Image implements Identified, Fingerprintable {
         * @return The creation time of this image (in milliseconds since 1970, Jan
         *         1, UTC)
         */
-       public long getCreationTime() {
-               return creationTime;
-       }
-
-       /**
-        * Sets the new creation time of this image. The creation time can only be
-        * set as long as no creation time has been set yet.
-        *
-        * @param creationTime
-        *            The new creation time of this image
-        * @return This image
-        */
-       public Image setCreationTime(long creationTime) {
-               checkArgument(creationTime > 0, "creationTime must be > 0");
-               checkState((this.creationTime == 0) || (this.creationTime == creationTime), "creationTime must not already be set");
-               this.creationTime = creationTime;
-               return this;
-       }
+       long getCreationTime();
 
        /**
         * Returns the width of this image.
         *
         * @return The width of this image (in pixels)
         */
-       public int getWidth() {
-               return width;
-       }
-
-       /**
-        * Sets the width of this image. The width can only be set as long as no
-        * width has been set yet.
-        *
-        * @param width
-        *            The new width of this image
-        * @return This image
-        */
-       public Image setWidth(int width) {
-               checkArgument(width > 0, "width must be > 0");
-               checkState((this.width == 0) || (this.width == width), "width must not already be set to another width");
-               this.width = width;
-               return this;
-       }
+       int getWidth();
 
        /**
         * Returns the height of this image.
         *
         * @return The height of this image (in pixels)
         */
-       public int getHeight() {
-               return height;
-       }
-
-       /**
-        * Sets the new height of this image. The height can only be set as long as
-        * no height has yet been set.
-        *
-        * @param height
-        *            The new height of this image
-        * @return This image
-        */
-       public Image setHeight(int height) {
-               checkArgument(height > 0, "height must be > 0");
-               checkState((this.height == 0) || (this.height == height), "height must not already be set to another height");
-               this.height = height;
-               return this;
-       }
+       int getHeight();
 
        /**
         * Returns the title of this image.
         *
         * @return The title of this image
         */
-       public String getTitle() {
-               return title;
-       }
-
-       /**
-        * Sets the title of this image.
-        *
-        * @param title
-        *            The title of this image
-        * @return This image
-        */
-       public Image setTitle(String title) {
-               this.title = checkNotNull(title, "title must not be null");
-               return this;
-       }
+       String getTitle();
 
        /**
         * Returns the description of this image.
         *
         * @return The description of this image
         */
-       public String getDescription() {
-               return description;
-       }
-
-       /**
-        * Sets the description of this image.
-        *
-        * @param description
-        *            The description of this image
-        * @return This image
-        */
-       public Image setDescription(String description) {
-               this.description = checkNotNull(description, "description must not be null");
-               return this;
-       }
-
-       //
-       // FINGERPRINTABLE METHODS
-       //
+       String getDescription();
 
        /**
         * {@inheritDoc}
         */
        @Override
-       public String getFingerprint() {
-               Hasher hash = Hashing.sha256().newHasher();
-               hash.putString("Image(");
-               hash.putString("ID(").putString(id).putString(")");
-               hash.putString("Title(").putString(title).putString(")");
-               hash.putString("Description(").putString(description).putString(")");
-               hash.putString(")");
-               return hash.hash().toString();
-       }
+       String getFingerprint();
 
-       //
-       // OBJECT METHODS
-       //
+       Modifier modify() throws IllegalStateException;
 
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public int hashCode() {
-               return id.hashCode();
-       }
+       interface Modifier {
+
+               Modifier setSone(Sone sone);
+
+               Modifier setCreationTime(long creationTime);
+
+               Modifier setKey(String key);
+
+               Modifier setTitle(String title);
+
+               Modifier setDescription(String description);
+
+               Modifier setWidth(int width);
+
+               Modifier setHeight(int height);
+
+               Image update() throws IllegalStateException;
 
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public boolean equals(Object object) {
-               if (!(object instanceof Image)) {
-                       return false;
-               }
-               return ((Image) object).id.equals(id);
        }
 
 }
diff --git a/src/main/java/net/pterodactylus/sone/data/ImageImpl.java b/src/main/java/net/pterodactylus/sone/data/ImageImpl.java
new file mode 100644 (file)
index 0000000..58741c3
--- /dev/null
@@ -0,0 +1,257 @@
+/*
+ * Sone - ImageImpl.java - Copyright © 2011–2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package net.pterodactylus.sone.data;
+
+import static com.google.common.base.Optional.absent;
+import static com.google.common.base.Optional.fromNullable;
+import static com.google.common.base.Optional.of;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import java.util.UUID;
+
+import com.google.common.base.Optional;
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
+
+/**
+ * Container for image metadata.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class ImageImpl implements Image {
+
+       /** The ID of the image. */
+       private final String id;
+
+       /** The Sone the image belongs to. */
+       private Sone sone;
+
+       /** The album this image belongs to. */
+       private Album album;
+
+       /** The request key of the image. */
+       private String key;
+
+       /** The creation time of the image. */
+       private long creationTime;
+
+       /** The width of the image. */
+       private int width;
+
+       /** The height of the image. */
+       private int height;
+
+       /** The title of the image. */
+       private String title;
+
+       /** The description of the image. */
+       private String description;
+
+       /** Creates a new image with a random ID. */
+       public ImageImpl() {
+               this(UUID.randomUUID().toString());
+               this.creationTime = System.currentTimeMillis();
+       }
+
+       /**
+        * Creates a new image.
+        *
+        * @param id
+        *              The ID of the image
+        */
+       public ImageImpl(String id) {
+               this.id = checkNotNull(id, "id must not be null");
+       }
+
+       //
+       // ACCESSORS
+       //
+
+       @Override
+       public String getId() {
+               return id;
+       }
+
+       @Override
+       public Sone getSone() {
+               return sone;
+       }
+
+       @Override
+       public Album getAlbum() {
+               return album;
+       }
+
+       @Override
+       public Image setAlbum(Album album) {
+               checkNotNull(album, "album must not be null");
+               checkNotNull(album.getSone().equals(getSone()), "album must belong to the same Sone as this image");
+               this.album = album;
+               return this;
+       }
+
+       @Override
+       public String getKey() {
+               return key;
+       }
+
+       @Override
+       public boolean isInserted() {
+               return key != null;
+       }
+
+       @Override
+       public long getCreationTime() {
+               return creationTime;
+       }
+
+       @Override
+       public int getWidth() {
+               return width;
+       }
+
+       @Override
+       public int getHeight() {
+               return height;
+       }
+
+       @Override
+       public String getTitle() {
+               return title;
+       }
+
+       @Override
+       public String getDescription() {
+               return description;
+       }
+
+       public Modifier modify() throws IllegalStateException {
+               checkState((sone == null) || sone.isLocal(), "only local images may be modified");
+               return new Modifier() {
+                       private Optional<Sone> sone = absent();
+
+                       private Optional<Long> creationTime = absent();
+
+                       private Optional<String> key = absent();
+
+                       private Optional<String> title = absent();
+
+                       private Optional<String> description = absent();
+
+                       private Optional<Integer> width = absent();
+
+                       private Optional<Integer> height = absent();
+
+                       @Override
+                       public Modifier setSone(Sone sone) {
+                               this.sone = fromNullable(sone);
+                               return this;
+                       }
+
+                       @Override
+                       public Modifier setCreationTime(long creationTime) {
+                               this.creationTime = of(creationTime);
+                               return this;
+                       }
+
+                       @Override
+                       public Modifier setKey(String key) {
+                               this.key = fromNullable(key);
+                               return this;
+                       }
+
+                       @Override
+                       public Modifier setTitle(String title) {
+                               this.title = fromNullable(title);
+                               return this;
+                       }
+
+                       @Override
+                       public Modifier setDescription(String description) {
+                               this.description = fromNullable(description);
+                               return this;
+                       }
+
+                       @Override
+                       public Modifier setWidth(int width) {
+                               this.width = of(width);
+                               return this;
+                       }
+
+                       @Override
+                       public Modifier setHeight(int height) {
+                               this.height = of(height);
+                               return this;
+                       }
+
+                       @Override
+                       public Image update() throws IllegalStateException {
+                               checkState(!sone.isPresent() || sone.get().equals(ImageImpl.this.sone), "can not change Sone once set");
+                               checkState(!creationTime.isPresent() || (ImageImpl.this.creationTime == 0), "can not change creation time once set");
+                               checkState(!key.isPresent() || key.get().equals(ImageImpl.this.key), "can not change key once set");
+                               checkState(!width.isPresent() || width.get().equals(ImageImpl.this.width), "can not change width once set");
+                               checkState(!height.isPresent() || height.get().equals(ImageImpl.this.height), "can not change height once set");
+
+                               ImageImpl.this.sone = sone.or(ImageImpl.this.sone);
+                               ImageImpl.this.creationTime = creationTime.or(ImageImpl.this.creationTime);
+                               ImageImpl.this.key = key.or(ImageImpl.this.key);
+                               ImageImpl.this.title = title.or(ImageImpl.this.title);
+                               ImageImpl.this.description = description.or(ImageImpl.this.description);
+                               ImageImpl.this.width = width.or(ImageImpl.this.width);
+                               ImageImpl.this.height = height.or(ImageImpl.this.height);
+
+                               return ImageImpl.this;
+                       }
+               };
+       }
+
+       //
+       // FINGERPRINTABLE METHODS
+       //
+
+       @Override
+       public String getFingerprint() {
+               Hasher hash = Hashing.sha256().newHasher();
+               hash.putString("Image(");
+               hash.putString("ID(").putString(id).putString(")");
+               hash.putString("Title(").putString(title).putString(")");
+               hash.putString("Description(").putString(description).putString(")");
+               hash.putString(")");
+               return hash.hash().toString();
+       }
+
+       //
+       // OBJECT METHODS
+       //
+
+       /** {@inheritDoc} */
+       @Override
+       public int hashCode() {
+               return id.hashCode();
+       }
+
+       /** {@inheritDoc} */
+       @Override
+       public boolean equals(Object object) {
+               if (!(object instanceof ImageImpl)) {
+                       return false;
+               }
+               return ((ImageImpl) object).id.equals(id);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/data/impl/AbstractImageBuilder.java b/src/main/java/net/pterodactylus/sone/data/impl/AbstractImageBuilder.java
new file mode 100644 (file)
index 0000000..533ba39
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Sone - AbstractImageBuilder.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package net.pterodactylus.sone.data.impl;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import net.pterodactylus.sone.database.ImageBuilder;
+
+/**
+ * Abstract {@link ImageBuilder} implementation. It stores the state of the new
+ * album and performs validation, you only need to implement {@link #build()}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public abstract class AbstractImageBuilder implements ImageBuilder {
+
+       /** Whether to create an album with a random ID. */
+       protected boolean randomId;
+
+       /** The ID of the album to create. */
+       protected String id;
+
+       @Override
+       public ImageBuilder randomId() {
+               randomId = true;
+               return this;
+       }
+
+       @Override
+       public ImageBuilder withId(String id) {
+               this.id = id;
+               return this;
+       }
+
+       //
+       // PROTECTED METHODS
+       //
+
+       /**
+        * Validates the state of this image builder.
+        *
+        * @throws IllegalStateException
+        *              if the state is not valid for building a new image
+        */
+       protected void validate() throws IllegalStateException {
+               checkState((randomId && (id == null)) || (!randomId && (id != null)), "exactly one of random ID or custom ID must be set");
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/data/impl/ImageBuilderImpl.java b/src/main/java/net/pterodactylus/sone/data/impl/ImageBuilderImpl.java
new file mode 100644 (file)
index 0000000..870b5d7
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Sone - ImageBuilderImpl.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.data.impl;
+
+import net.pterodactylus.sone.data.Image;
+import net.pterodactylus.sone.data.ImageImpl;
+import net.pterodactylus.sone.database.ImageBuilder;
+
+/**
+ * {@link ImageBuilder} implementation that creates {@link ImageImpl} objects.
+ *
+ * @author <a href="mailto:d.roden@xplosion.de">David Roden</a>
+ */
+public class ImageBuilderImpl extends AbstractImageBuilder {
+
+       @Override
+       public Image build() throws IllegalStateException {
+               validate();
+               return randomId ? new ImageImpl() : new ImageImpl(id);
+       }
+
+}
index 4bf863f..ee7a9af 100644 (file)
@@ -26,7 +26,7 @@ import com.google.common.util.concurrent.Service;
  *
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
-public interface Database extends Service, PostDatabase, PostReplyDatabase, AlbumDatabase {
+public interface Database extends Service, PostDatabase, PostReplyDatabase, AlbumDatabase, ImageDatabase {
 
        /**
         * Saves the database.
diff --git a/src/main/java/net/pterodactylus/sone/database/ImageBuilder.java b/src/main/java/net/pterodactylus/sone/database/ImageBuilder.java
new file mode 100644 (file)
index 0000000..af405c7
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Sone - ImageBuilder.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package net.pterodactylus.sone.database;
+
+import net.pterodactylus.sone.data.Image;
+
+/**
+ * Builder for {@link Image} objects.
+ *
+ * @author <a href="mailto:d.roden@xplosion.de">David Roden</a>
+ */
+public interface ImageBuilder {
+
+       ImageBuilder randomId();
+
+       ImageBuilder withId(String id);
+
+       Image build() throws IllegalStateException;
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/database/ImageBuilderFactory.java b/src/main/java/net/pterodactylus/sone/database/ImageBuilderFactory.java
new file mode 100644 (file)
index 0000000..29b605b
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Sone - ImageBuilderFactory.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.database;
+
+/**
+ * Factory for {@link ImageBuilder}s.
+ *
+ * @author <a href="mailto:d.roden@xplosion.de">David Roden</a>
+ */
+public interface ImageBuilderFactory {
+
+       ImageBuilder newImageBuilder();
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/database/ImageDatabase.java b/src/main/java/net/pterodactylus/sone/database/ImageDatabase.java
new file mode 100644 (file)
index 0000000..b5e436d
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Sone - ImageDatabase.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.database;
+
+/**
+ * Combines an {@link ImageProvider}, an {@link ImageBuilderFactory}, and an
+ * {@link ImageStore} into an image database.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface ImageDatabase extends ImageProvider, ImageBuilderFactory, ImageStore {
+
+       /* nothing here. */
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/database/ImageProvider.java b/src/main/java/net/pterodactylus/sone/database/ImageProvider.java
new file mode 100644 (file)
index 0000000..c9ad9e1
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Sone - ImageProvider.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.database;
+
+import net.pterodactylus.sone.data.Image;
+
+import com.google.common.base.Optional;
+
+/**
+ * Provides {@link Image}.
+ *
+ * @author <a href="mailto:d.roden@xplosion.de">David Roden</a>
+ */
+public interface ImageProvider {
+
+       Optional<Image> getImage(String imageId);
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/database/ImageStore.java b/src/main/java/net/pterodactylus/sone/database/ImageStore.java
new file mode 100644 (file)
index 0000000..f09eff0
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Sone - ImageStore.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.database;
+
+import net.pterodactylus.sone.data.Image;
+
+/**
+ * Manages {@link Image} storage.
+ *
+ * @author <a href="mailto:d.roden@xplosion.de">David Roden</a>
+ */
+public interface ImageStore {
+
+       void storeImage(Image image);
+
+       void removeImage(Image image);
+
+}
index 77ff2f4..8b25954 100644 (file)
@@ -35,14 +35,17 @@ import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 import net.pterodactylus.sone.data.Album;
+import net.pterodactylus.sone.data.Image;
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Reply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.data.impl.AlbumBuilderImpl;
+import net.pterodactylus.sone.data.impl.ImageBuilderImpl;
 import net.pterodactylus.sone.database.AlbumBuilder;
 import net.pterodactylus.sone.database.Database;
 import net.pterodactylus.sone.database.DatabaseException;
+import net.pterodactylus.sone.database.ImageBuilder;
 import net.pterodactylus.sone.database.PostBuilder;
 import net.pterodactylus.sone.database.PostDatabase;
 import net.pterodactylus.sone.database.PostReplyBuilder;
@@ -104,6 +107,8 @@ public class MemoryDatabase extends AbstractService implements Database {
 
        private final Map<String, Album> allAlbums = new HashMap<String, Album>();
 
+       private final Map<String, Image> allImages = new HashMap<String, Image>();
+
        /**
         * Creates a new memory database.
         *
@@ -465,6 +470,53 @@ public class MemoryDatabase extends AbstractService implements Database {
        }
 
        //
+       // IMAGEPROVIDER METHODS
+       //
+
+       @Override
+       public Optional<Image> getImage(String imageId) {
+               lock.readLock().lock();
+               try {
+                       return fromNullable(allImages.get(imageId));
+               } finally {
+                       lock.readLock().unlock();
+               }
+       }
+
+       //
+       // IMAGEBUILDERFACTORY METHODS
+       //
+
+       @Override
+       public ImageBuilder newImageBuilder() {
+               return new ImageBuilderImpl();
+       }
+
+       //
+       // IMAGESTORE METHODS
+       //
+
+       @Override
+       public void storeImage(Image image) {
+               lock.writeLock().lock();
+               try {
+                       allImages.put(image.getId(), image);
+               } finally {
+                       lock.writeLock().unlock();
+               }
+       }
+
+       @Override
+       public void removeImage(Image image) {
+               lock.writeLock().lock();
+               try {
+                       allImages.remove(image.getId());
+               } finally {
+                       lock.writeLock().unlock();
+               }
+       }
+
+       //
        // PACKAGE-PRIVATE METHODS
        //
 
index 75872eb..9a29c85 100644 (file)
@@ -73,8 +73,7 @@ public class EditImagePage extends SoneTemplatePage {
                                if (title.length() == 0) {
                                        templateContext.set("titleMissing", true);
                                }
-                               image.setTitle(title);
-                               image.setDescription(TextFilter.filter(request.getHttpRequest().getHeader("host"), description));
+                               image.modify().setTitle(title).setDescription(TextFilter.filter(request.getHttpRequest().getHeader("host"), description)).update();
                        }
                        webInterface.getCore().touchConfiguration();
                        throw new RedirectException(returnPage);
index 2800f29..3844082 100644 (file)
@@ -123,7 +123,7 @@ public class UploadImagePage extends SoneTemplatePage {
                                String mimeType = getMimeType(imageData);
                                TemporaryImage temporaryImage = webInterface.getCore().createTemporaryImage(mimeType, imageData);
                                image = webInterface.getCore().createImage(currentSone, parent, temporaryImage);
-                               image.setTitle(name).setDescription(TextFilter.filter(request.getHttpRequest().getHeader("host"), description)).setWidth(uploadedImage.getWidth(null)).setHeight(uploadedImage.getHeight(null));
+                               image.modify().setTitle(name).setDescription(TextFilter.filter(request.getHttpRequest().getHeader("host"), description)).setWidth(uploadedImage.getWidth(null)).setHeight(uploadedImage.getHeight(null)).update();
                        } catch (IOException ioe1) {
                                logger.log(Level.WARNING, "Could not read uploaded image!", ioe1);
                                return;
index 08452ef..0c45225 100644 (file)
@@ -78,7 +78,7 @@ public class EditImageAjaxPage extends JsonPage {
                }
                String title = request.getHttpRequest().getParam("title").trim();
                String description = request.getHttpRequest().getParam("description").trim();
-               image.setTitle(title).setDescription(TextFilter.filter(request.getHttpRequest().getHeader("host"), description));
+               image.modify().setTitle(title).setDescription(TextFilter.filter(request.getHttpRequest().getHeader("host"), description)).update();
                webInterface.getCore().touchConfiguration();
                return createSuccessJsonObject().put("imageId", image.getId()).put("title", image.getTitle()).put("description", image.getDescription()).put("parsedDescription", (String) parserFilter.format(new TemplateContext(), image.getDescription(), ImmutableMap.<String, Object>builder().put("sone", image.getSone()).build()));
        }