From: David ‘Bombe’ Roden Date: Tue, 20 Sep 2011 20:03:35 +0000 (+0200) Subject: Merge branch 'next' into dev/image X-Git-Tag: 0.7^2~2^2~69 X-Git-Url: https://git.pterodactylus.net/?p=Sone.git;a=commitdiff_plain;h=0df5e91852f737d760c5a9f54c5667309fbadcc2 Merge branch 'next' into dev/image --- 0df5e91852f737d760c5a9f54c5667309fbadcc2 diff --cc pom.xml index a849a98,037d3be..e59d521 --- a/pom.xml +++ b/pom.xml @@@ -7,7 -7,7 +7,7 @@@ net.pterodactylus utils - 0.9.4-SNAPSHOT - 0.10.0 ++ 0.10.1-SNAPSHOT junit diff --cc src/main/java/net/pterodactylus/sone/core/Core.java index 343fe69,0eaf296..e67bf68 --- a/src/main/java/net/pterodactylus/sone/core/Core.java +++ b/src/main/java/net/pterodactylus/sone/core/Core.java @@@ -31,15 -34,14 +34,17 @@@ import java.util.logging.Logger import net.pterodactylus.sone.core.Options.DefaultOption; import net.pterodactylus.sone.core.Options.Option; import net.pterodactylus.sone.core.Options.OptionWatcher; +import net.pterodactylus.sone.data.Album; import net.pterodactylus.sone.data.Client; +import net.pterodactylus.sone.data.Image; import net.pterodactylus.sone.data.Post; import net.pterodactylus.sone.data.Profile; --import net.pterodactylus.sone.data.Profile.Field; import net.pterodactylus.sone.data.Reply; import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.data.TemporaryImage; ++import net.pterodactylus.sone.data.Profile.Field; + import net.pterodactylus.sone.fcp.FcpInterface; + import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired; import net.pterodactylus.sone.freenet.wot.Identity; import net.pterodactylus.sone.freenet.wot.IdentityListener; import net.pterodactylus.sone.freenet.wot.IdentityManager; @@@ -60,7 -67,7 +70,7 @@@ import freenet.keys.FreenetURI * * @author David ‘Bombe’ Roden */ - public class Core implements IdentityListener, UpdateListener, ImageInsertListener { -public class Core extends AbstractService implements IdentityListener, UpdateListener, SoneProvider, PostProvider, SoneInsertListener { ++public class Core extends AbstractService implements IdentityListener, UpdateListener, SoneProvider, PostProvider, SoneInsertListener, ImageInsertListener { /** * Enumeration for the possible states of a {@link Sone}. @@@ -109,9 -116,9 +119,12 @@@ /** The Sone downloader. */ private final SoneDownloader soneDownloader; + /** The image inserter. */ + private final ImageInserter imageInserter; + + /** Sone downloader thread-pool. */ + private final ExecutorService soneDownloaders = Executors.newFixedThreadPool(10); + /** The update checker. */ private final UpdateChecker updateChecker; @@@ -171,15 -182,12 +188,21 @@@ /** Trusted identities, sorted by own identities. */ private Map> trustedIdentities = Collections.synchronizedMap(new HashMap>()); + /** All known albums. */ + private Map albums = new HashMap(); + + /** All known images. */ + private Map images = new HashMap(); + + /** All temporary images. */ + private Map temporaryImages = new HashMap(); + + /** Ticker for threads that mark own elements as known. */ + private Ticker localElementTicker = new Ticker(); + + /** The time the configuration was last touched. */ + private volatile long lastConfigurationUpdate; + /** * Creates a new core. * @@@ -1370,67 -1327,9 +1427,67 @@@ friends.add(friendId); } + /* load albums. */ + List topLevelAlbums = new ArrayList(); + int albumCounter = 0; + while (true) { + String albumPrefix = sonePrefix + "/Albums/" + albumCounter++; + String albumId = configuration.getStringValue(albumPrefix + "/ID").getValue(null); + if (albumId == null) { + break; + } + String albumTitle = configuration.getStringValue(albumPrefix + "/Title").getValue(null); + String albumDescription = configuration.getStringValue(albumPrefix + "/Description").getValue(null); + String albumParentId = configuration.getStringValue(albumPrefix + "/Parent").getValue(null); + if ((albumTitle == null) || (albumDescription == null)) { + logger.log(Level.WARNING, "Invalid album found, aborting load!"); + return; + } + Album album = getAlbum(albumId).setSone(sone).setTitle(albumTitle).setDescription(albumDescription); + if (albumParentId != null) { + Album parentAlbum = getAlbum(albumParentId, false); + if (parentAlbum == null) { + logger.log(Level.WARNING, "Invalid parent album ID: " + albumParentId); + return; + } + parentAlbum.addAlbum(album); + } else { + topLevelAlbums.add(album); + } + } + + /* load images. */ + int imageCounter = 0; + while (true) { + String imagePrefix = sonePrefix + "/Images/" + imageCounter++; + String imageId = configuration.getStringValue(imagePrefix + "/ID").getValue(null); + if (imageId == null) { + break; + } + String albumId = configuration.getStringValue(imagePrefix + "/Album").getValue(null); + String key = configuration.getStringValue(imagePrefix + "/Key").getValue(null); + String title = configuration.getStringValue(imagePrefix + "/Title").getValue(null); + String description = configuration.getStringValue(imagePrefix + "/Description").getValue(null); + Long creationTime = configuration.getLongValue(imagePrefix + "/CreationTime").getValue(null); + Integer width = configuration.getIntValue(imagePrefix + "/Width").getValue(null); + Integer height = configuration.getIntValue(imagePrefix + "/Height").getValue(null); + if ((albumId == null) || (key == null) || (title == null) || (description == null) || (creationTime == null) || (width == null) || (height == null)) { + logger.log(Level.WARNING, "Invalid image found, aborting load!"); + return; + } + Album album = getAlbum(albumId, false); + if (album == null) { + 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); + album.addImage(image); + } + /* load options. */ - sone.getOptions().addBooleanOption("AutoFollow", new DefaultOption(false)); sone.getOptions().getBooleanOption("AutoFollow").set(configuration.getBooleanValue(sonePrefix + "/Options/AutoFollow").getValue(null)); + sone.getOptions().getBooleanOption("EnableSoneInsertNotifications").set(configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").getValue(null)); /* if we’re still here, Sone was loaded successfully. */ synchronized (sone) { @@@ -1842,153 -1629,23 +1788,167 @@@ } /** + * Creates a new top-level album for the given Sone. + * + * @param sone + * The Sone to create the album for + * @return The new album + */ + public Album createAlbum(Sone sone) { + return createAlbum(sone, null); + } + + /** + * 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 = new Album(); + synchronized (albums) { + albums.put(album.getId(), album); + } + album.setSone(sone); + if (parent != null) { + parent.addAlbum(album); + } else { + sone.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) { + Validation.begin().isNotNull("Album", album).check().is("Local Sone", isLocalSone(album.getSone())).check(); + if (!album.isEmpty()) { + return; + } + if (album.getParent() == null) { + album.getSone().removeAlbum(album); + } else { + album.getParent().removeAlbum(album); + } + synchronized (albums) { + albums.remove(album.getId()); + } + saveSone(album.getSone()); + } + + /** + * 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) { + Validation.begin().isNotNull("Sone", sone).isNotNull("Album", album).isNotNull("Temporary Image", temporaryImage).check().is("Local Sone", isLocalSone(sone)).check().isEqual("Owner and Album Owner", sone, album.getSone()).check(); + Image image = new Image(temporaryImage.getId()).setSone(sone).setCreationTime(System.currentTimeMillis()); + album.addImage(image); + synchronized (images) { + images.put(image.getId(), image); + } + imageInserter.insertImage(temporaryImage, image); + return image; + } + + /** + * Deletes the given image. This method will also delete a matching + * temporary image. + * + * @see #deleteTemporaryImage(TemporaryImage) + * @param image + * The image to delete + */ + public void deleteImage(Image image) { + Validation.begin().isNotNull("Image", image).check().is("Local Sone", isLocalSone(image.getSone())).check(); + deleteTemporaryImage(image.getId()); + image.getAlbum().removeImage(image); + synchronized (images) { + images.remove(image.getId()); + } + saveSone(image.getSone()); + } + + /** + * 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 given temporary image. + * + * @param temporaryImage + * The temporary image to delete + */ + public void deleteTemporaryImage(TemporaryImage temporaryImage) { + Validation.begin().isNotNull("Temporary Image", temporaryImage).check(); + deleteTemporaryImage(temporaryImage.getId()); + } + + /** + * Deletes the temporary image with the given ID. + * + * @param imageId + * The ID of the temporary image to delete + */ + public void deleteTemporaryImage(String imageId) { + Validation.begin().isNotNull("Temporary Image ID", imageId).check(); + 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(); + } + + // + // SERVICE METHODS + // + + /** * Starts the core. */ - public void start() { + @Override + public void serviceStart() { loadConfiguration(); updateChecker.addUpdateListener(this); updateChecker.start(); @@@ -2006,8 -1684,110 +1987,143 @@@ updateChecker.stop(); updateChecker.removeUpdateListener(this); soneDownloader.stop(); - saveConfiguration(); - stopped = true; + } + + // + // PRIVATE METHODS + // + + /** + * Saves the given Sone. This will persist all local settings for the given + * Sone, such as the friends list and similar, private options. + * + * @param sone + * The Sone to save + */ + private synchronized void saveSone(Sone sone) { + if (!isLocalSone(sone)) { + logger.log(Level.FINE, "Tried to save non-local Sone: %s", sone); + return; + } + if (!(sone.getIdentity() instanceof OwnIdentity)) { + logger.log(Level.WARNING, "Local Sone without OwnIdentity found, refusing to save: %s", sone); + return; + } + + logger.log(Level.INFO, "Saving Sone: %s", sone); + try { + ((OwnIdentity) sone.getIdentity()).setProperty("Sone.LatestEdition", String.valueOf(sone.getLatestEdition())); + + /* save Sone into configuration. */ + String sonePrefix = "Sone/" + sone.getId(); + configuration.getLongValue(sonePrefix + "/Time").setValue(sone.getTime()); + configuration.getStringValue(sonePrefix + "/LastInsertFingerprint").setValue(soneInserters.get(sone).getLastInsertFingerprint()); + + /* save profile. */ + Profile profile = sone.getProfile(); + configuration.getStringValue(sonePrefix + "/Profile/FirstName").setValue(profile.getFirstName()); + configuration.getStringValue(sonePrefix + "/Profile/MiddleName").setValue(profile.getMiddleName()); + configuration.getStringValue(sonePrefix + "/Profile/LastName").setValue(profile.getLastName()); + configuration.getIntValue(sonePrefix + "/Profile/BirthDay").setValue(profile.getBirthDay()); + configuration.getIntValue(sonePrefix + "/Profile/BirthMonth").setValue(profile.getBirthMonth()); + configuration.getIntValue(sonePrefix + "/Profile/BirthYear").setValue(profile.getBirthYear()); + + /* save profile fields. */ + int fieldCounter = 0; + for (Field profileField : profile.getFields()) { + String fieldPrefix = sonePrefix + "/Profile/Fields/" + fieldCounter++; + configuration.getStringValue(fieldPrefix + "/Name").setValue(profileField.getName()); + configuration.getStringValue(fieldPrefix + "/Value").setValue(profileField.getValue()); + } + configuration.getStringValue(sonePrefix + "/Profile/Fields/" + fieldCounter + "/Name").setValue(null); + + /* save posts. */ + int postCounter = 0; + for (Post post : sone.getPosts()) { + String postPrefix = sonePrefix + "/Posts/" + postCounter++; + configuration.getStringValue(postPrefix + "/ID").setValue(post.getId()); + configuration.getStringValue(postPrefix + "/Recipient").setValue((post.getRecipient() != null) ? post.getRecipient().getId() : null); + configuration.getLongValue(postPrefix + "/Time").setValue(post.getTime()); + configuration.getStringValue(postPrefix + "/Text").setValue(post.getText()); + } + configuration.getStringValue(sonePrefix + "/Posts/" + postCounter + "/ID").setValue(null); + + /* save replies. */ + int replyCounter = 0; + for (Reply reply : sone.getReplies()) { + String replyPrefix = sonePrefix + "/Replies/" + replyCounter++; + configuration.getStringValue(replyPrefix + "/ID").setValue(reply.getId()); + configuration.getStringValue(replyPrefix + "/Post/ID").setValue(reply.getPost().getId()); + configuration.getLongValue(replyPrefix + "/Time").setValue(reply.getTime()); + configuration.getStringValue(replyPrefix + "/Text").setValue(reply.getText()); + } + configuration.getStringValue(sonePrefix + "/Replies/" + replyCounter + "/ID").setValue(null); + + /* save post likes. */ + int postLikeCounter = 0; + for (String postId : sone.getLikedPostIds()) { + configuration.getStringValue(sonePrefix + "/Likes/Post/" + postLikeCounter++ + "/ID").setValue(postId); + } + configuration.getStringValue(sonePrefix + "/Likes/Post/" + postLikeCounter + "/ID").setValue(null); + + /* save reply likes. */ + int replyLikeCounter = 0; + for (String replyId : sone.getLikedReplyIds()) { + configuration.getStringValue(sonePrefix + "/Likes/Reply/" + replyLikeCounter++ + "/ID").setValue(replyId); + } + configuration.getStringValue(sonePrefix + "/Likes/Reply/" + replyLikeCounter + "/ID").setValue(null); + + /* save friends. */ + int friendCounter = 0; + for (String friendId : sone.getFriends()) { + configuration.getStringValue(sonePrefix + "/Friends/" + friendCounter++ + "/ID").setValue(friendId); + } + configuration.getStringValue(sonePrefix + "/Friends/" + friendCounter + "/ID").setValue(null); + ++ /* save albums. first, collect in a flat structure, top-level first. */ ++ List albums = Sone.flattenAlbums(sone.getAlbums()); ++ ++ int albumCounter = 0; ++ for (Album album : albums) { ++ String albumPrefix = sonePrefix + "/Albums/" + albumCounter++; ++ configuration.getStringValue(albumPrefix + "/ID").setValue(album.getId()); ++ configuration.getStringValue(albumPrefix + "/Title").setValue(album.getTitle()); ++ configuration.getStringValue(albumPrefix + "/Description").setValue(album.getDescription()); ++ configuration.getStringValue(albumPrefix + "/Parent").setValue(album.getParent() == null ? null : album.getParent().getId()); ++ } ++ configuration.getStringValue(sonePrefix + "/Albums/" + albumCounter + "/ID").setValue(null); ++ ++ /* save images. */ ++ int imageCounter = 0; ++ for (Album album : albums) { ++ for (Image image : album.getImages()) { ++ if (!image.isInserted()) { ++ continue; ++ } ++ String imagePrefix = sonePrefix + "/Images/" + imageCounter++; ++ configuration.getStringValue(imagePrefix + "/ID").setValue(image.getId()); ++ configuration.getStringValue(imagePrefix + "/Album").setValue(album.getId()); ++ configuration.getStringValue(imagePrefix + "/Key").setValue(image.getKey()); ++ configuration.getStringValue(imagePrefix + "/Title").setValue(image.getTitle()); ++ configuration.getStringValue(imagePrefix + "/Description").setValue(image.getDescription()); ++ configuration.getLongValue(imagePrefix + "/CreationTime").setValue(image.getCreationTime()); ++ configuration.getIntValue(imagePrefix + "/Width").setValue(image.getWidth()); ++ configuration.getIntValue(imagePrefix + "/Height").setValue(image.getHeight()); ++ } ++ } ++ configuration.getStringValue(sonePrefix + "/Images/" + imageCounter + "/ID").setValue(null); ++ + /* save options. */ + configuration.getBooleanValue(sonePrefix + "/Options/AutoFollow").setValue(sone.getOptions().getBooleanOption("AutoFollow").getReal()); + configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").setValue(sone.getOptions().getBooleanOption("EnableSoneInsertNotifications").getReal()); + + configuration.save(); + logger.log(Level.INFO, "Sone %s saved.", sone); + } catch (ConfigurationException ce1) { + logger.log(Level.WARNING, "Could not save Sone: " + sone, ce1); + } catch (WebOfTrustException wote1) { + logger.log(Level.WARNING, "Could not set WoT property for Sone: " + sone, wote1); + } } /** @@@ -2273,42 -2135,26 +2471,70 @@@ /** * {@inheritDoc} */ + @Override + public void insertStarted(Sone sone) { + coreListenerManager.fireSoneInserting(sone); + } + + /** + * {@inheritDoc} + */ + @Override + public void insertFinished(Sone sone, long insertDuration) { + coreListenerManager.fireSoneInserted(sone, insertDuration); + } + + /** + * {@inheritDoc} + */ + @Override + public void insertAborted(Sone sone, Throwable cause) { + coreListenerManager.fireSoneInsertAborted(sone, cause); + } + ++ // ++ // SONEINSERTLISTENER METHODS ++ // ++ ++ /** ++ * {@inheritDoc} ++ */ ++ @Override + public void imageInsertStarted(Image image) { + logger.log(Level.WARNING, "Image insert started for " + image); + coreListenerManager.fireImageInsertStarted(image); + } + + /** + * {@inheritDoc} + */ + @Override + public void imageInsertAborted(Image image) { + logger.log(Level.WARNING, "Image insert aborted for " + image); + coreListenerManager.fireImageInsertAborted(image); + } + + /** + * {@inheritDoc} + */ + @Override + public void imageInsertFinished(Image image, FreenetURI key) { + logger.log(Level.WARNING, "Image insert finished for " + image + ": " + key); + image.setKey(key.toString()); + deleteTemporaryImage(image.getId()); + saveSone(image.getSone()); + coreListenerManager.fireImageInsertFinished(image); + } + + /** + * {@inheritDoc} + */ + @Override + public void imageInsertFailed(Image image, Throwable cause) { + logger.log(Level.WARNING, "Image insert failed for " + image, cause); + coreListenerManager.fireImageInsertFailed(image, cause); + } + /** * Convenience interface for external classes that want to access the core’s * configuration. diff --cc src/main/java/net/pterodactylus/sone/core/SoneDownloader.java index 8d0d1f2,0812f66..4da1226 --- a/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java +++ b/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java @@@ -26,11 -25,8 +26,10 @@@ import java.util.Set import java.util.logging.Level; import java.util.logging.Logger; - import net.pterodactylus.sone.core.Core.Preferences; import net.pterodactylus.sone.core.Core.SoneStatus; +import net.pterodactylus.sone.data.Album; import net.pterodactylus.sone.data.Client; +import net.pterodactylus.sone.data.Image; import net.pterodactylus.sone.data.Post; import net.pterodactylus.sone.data.Profile; import net.pterodactylus.sone.data.Reply; diff --cc src/main/java/net/pterodactylus/sone/core/SoneInserter.java index 2160ab8,f654d09..44f8bbc --- a/src/main/java/net/pterodactylus/sone/core/SoneInserter.java +++ b/src/main/java/net/pterodactylus/sone/core/SoneInserter.java @@@ -266,11 -298,10 +298,11 @@@ public class SoneInserter extends Abstr soneProperties.put("requestUri", sone.getRequestUri()); soneProperties.put("insertUri", sone.getInsertUri()); soneProperties.put("profile", sone.getProfile()); - soneProperties.put("posts", new ArrayList(sone.getPosts())); - soneProperties.put("replies", new HashSet(sone.getReplies())); + soneProperties.put("posts", new ListBuilder(new ArrayList(sone.getPosts())).sort(Post.TIME_COMPARATOR).get()); + soneProperties.put("replies", new ListBuilder(new ArrayList(sone.getReplies())).sort(new ReverseComparator(Reply.TIME_COMPARATOR)).get()); soneProperties.put("likedPostIds", new HashSet(sone.getLikedPostIds())); soneProperties.put("likedReplyIds", new HashSet(sone.getLikedReplyIds())); + soneProperties.put("albums", Sone.flattenAlbums(sone.getAlbums())); } // diff --cc src/main/java/net/pterodactylus/sone/web/CreateAlbumPage.java index afe95b7,0000000..a695952 mode 100644,000000..100644 --- a/src/main/java/net/pterodactylus/sone/web/CreateAlbumPage.java +++ b/src/main/java/net/pterodactylus/sone/web/CreateAlbumPage.java @@@ -1,72 -1,0 +1,73 @@@ +/* + * Sone - CreateAlbumPage.java - Copyright © 2011 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 . + */ + +package net.pterodactylus.sone.web; + +import net.pterodactylus.sone.data.Album; +import net.pterodactylus.sone.data.Sone; - import net.pterodactylus.sone.web.page.Page.Request.Method; ++import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; ++import net.pterodactylus.util.web.Method; + +/** + * Page that lets the user create a new album. + * + * @author David ‘Bombe’ Roden + */ +public class CreateAlbumPage extends SoneTemplatePage { + + /** + * Creates a new “create album” page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public CreateAlbumPage(Template template, WebInterface webInterface) { + super("createAlbum.html", template, "Page.CreateAlbum.Title", webInterface, true); + } + + // + // SONETEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override - protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException { ++ protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + super.processTemplate(request, templateContext); + if (request.getMethod() == Method.POST) { + String name = request.getHttpRequest().getPartAsStringFailsafe("name", 64).trim(); + if (name.length() == 0) { + templateContext.set("nameMissing", true); + return; + } + String description = request.getHttpRequest().getPartAsStringFailsafe("description", 256).trim(); + Sone currentSone = getCurrentSone(request.getToadletContext()); + String parentId = request.getHttpRequest().getPartAsStringFailsafe("parent", 36); + Album parent = webInterface.getCore().getAlbum(parentId, false); + Album album = webInterface.getCore().createAlbum(currentSone, parent); + album.setTitle(name).setDescription(description); - webInterface.getCore().saveSone(currentSone); ++ webInterface.getCore().touchConfiguration(); + throw new RedirectException("imageBrowser.html?album=" + album.getId()); + } + } + +} diff --cc src/main/java/net/pterodactylus/sone/web/DeleteAlbumPage.java index 6855e38,0000000..d03e065 mode 100644,000000..100644 --- a/src/main/java/net/pterodactylus/sone/web/DeleteAlbumPage.java +++ b/src/main/java/net/pterodactylus/sone/web/DeleteAlbumPage.java @@@ -1,77 -1,0 +1,78 @@@ +/* + * Sone - DeleteAlbumPage.java - Copyright © 2011 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 . + */ + +package net.pterodactylus.sone.web; + +import net.pterodactylus.sone.data.Album; - import net.pterodactylus.sone.web.page.Page.Request.Method; ++import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; ++import net.pterodactylus.util.web.Method; + +/** + * Page that lets the user delete an {@link Album}. + * + * @author David ‘Bombe’ Roden + */ +public class DeleteAlbumPage extends SoneTemplatePage { + + /** + * Creates a new “delete album” page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public DeleteAlbumPage(Template template, WebInterface webInterface) { + super("deleteAlbum.html", template, "Page.DeleteAlbum.Title", webInterface, true); + } + + /** + * {@inheritDoc} + */ + @Override - protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException { ++ protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + super.processTemplate(request, templateContext); + if (request.getMethod() == Method.POST) { + String albumId = request.getHttpRequest().getPartAsStringFailsafe("album", 36); + Album album = webInterface.getCore().getAlbum(albumId, false); + if (album == null) { + throw new RedirectException("invalid.html"); + } + if (!webInterface.getCore().isLocalSone(album.getSone())) { + throw new RedirectException("noPermission.html"); + } + if (request.getHttpRequest().isPartSet("abortDelete")) { + throw new RedirectException("imageBrowser.html?album=" + album.getId()); + } + Album parentAlbum = album.getParent(); + webInterface.getCore().deleteAlbum(album); + if (parentAlbum == null) { + throw new RedirectException("imageBrowser.html?sone=" + album.getSone().getId()); + } + throw new RedirectException("imageBrowser.html?album=" + parentAlbum.getId()); + } + String albumId = request.getHttpRequest().getParam("album"); + Album album = webInterface.getCore().getAlbum(albumId, false); + if (album == null) { + throw new RedirectException("invalid.html"); + } + templateContext.set("album", album); + } + +} diff --cc src/main/java/net/pterodactylus/sone/web/DeleteImagePage.java index 999a38a,0000000..66098ff mode 100644,000000..100644 --- a/src/main/java/net/pterodactylus/sone/web/DeleteImagePage.java +++ b/src/main/java/net/pterodactylus/sone/web/DeleteImagePage.java @@@ -1,72 -1,0 +1,73 @@@ +/* + * Sone - DeleteImagePage.java - Copyright © 2011 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 . + */ + +package net.pterodactylus.sone.web; + +import net.pterodactylus.sone.data.Image; - import net.pterodactylus.sone.web.page.Page.Request.Method; ++import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; ++import net.pterodactylus.util.web.Method; + +/** + * Page that lets the user delete an {@link Image}. + * + * @author David ‘Bombe’ Roden + */ +public class DeleteImagePage extends SoneTemplatePage { + + /** + * Creates a new “delete image” page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public DeleteImagePage(Template template, WebInterface webInterface) { + super("deleteImage.html", template, "Page.DeleteImage.Title", webInterface, true); + } + + // + // SONETEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override - protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException { ++ protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + super.processTemplate(request, templateContext); + String imageId = (request.getMethod() == Method.POST) ? request.getHttpRequest().getPartAsStringFailsafe("image", 36) : request.getHttpRequest().getParam("image"); + Image image = webInterface.getCore().getImage(imageId, false); + if (image == null) { + throw new RedirectException("invalid.html"); + } + if (!webInterface.getCore().isLocalSone(image.getSone())) { + throw new RedirectException("noPermission.html"); + } + if (request.getMethod() == Method.POST) { + if (request.getHttpRequest().isPartSet("abortDelete")) { + throw new RedirectException("imageBrowser.html?image=" + image.getId()); + } + webInterface.getCore().deleteImage(image); + throw new RedirectException("imageBrowser.html?album=" + image.getAlbum().getId()); + } + templateContext.set("image", image); + } + +} diff --cc src/main/java/net/pterodactylus/sone/web/EditAlbumPage.java index eaf8bf6,0000000..c3c2525 mode 100644,000000..100644 --- a/src/main/java/net/pterodactylus/sone/web/EditAlbumPage.java +++ b/src/main/java/net/pterodactylus/sone/web/EditAlbumPage.java @@@ -1,69 -1,0 +1,70 @@@ +/* + * Sone - EditAlbumPage.java - Copyright © 2011 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 . + */ + +package net.pterodactylus.sone.web; + +import net.pterodactylus.sone.data.Album; - import net.pterodactylus.sone.web.page.Page.Request.Method; ++import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; ++import net.pterodactylus.util.web.Method; + +/** + * TODO + * + * @author David ‘Bombe’ Roden + */ +public class EditAlbumPage extends SoneTemplatePage { + + /** + * TODO + * + * @param template + * @param webInterface + */ + public EditAlbumPage(Template template, WebInterface webInterface) { + super("editAlbum.html", template, "Page.EditAlbum.Title", webInterface, true); + } + + /** + * {@inheritDoc} + */ + @Override - protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException { ++ protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + super.processTemplate(request, templateContext); + if (request.getMethod() == Method.POST) { + String albumId = request.getHttpRequest().getPartAsStringFailsafe("album", 36); + Album album = webInterface.getCore().getAlbum(albumId, false); + if (album == null) { + throw new RedirectException("invalid.html"); + } + if (!webInterface.getCore().isLocalSone(album.getSone())) { + throw new RedirectException("noPermission.html"); + } + String title = request.getHttpRequest().getPartAsStringFailsafe("title", 100).trim(); + if (title.length() == 0) { + templateContext.set("titleMissing", true); + return; + } + String description = request.getHttpRequest().getPartAsStringFailsafe("description", 1000).trim(); + album.setTitle(title).setDescription(description); - webInterface.getCore().saveSone(album.getSone()); ++ webInterface.getCore().touchConfiguration(); + throw new RedirectException("imageBrowser.html?album=" + album.getId()); + } + } + +} diff --cc src/main/java/net/pterodactylus/sone/web/EditImagePage.java index eab3bb9,0000000..afb8787 mode 100644,000000..100644 --- a/src/main/java/net/pterodactylus/sone/web/EditImagePage.java +++ b/src/main/java/net/pterodactylus/sone/web/EditImagePage.java @@@ -1,75 -1,0 +1,76 @@@ +/* + * FreenetSone - WebInterface.java - Copyright © 2010 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 . + */ + +package net.pterodactylus.sone.web; + +import net.pterodactylus.sone.data.Image; - import net.pterodactylus.sone.web.page.Page.Request.Method; ++import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; ++import net.pterodactylus.util.web.Method; + +/** + * Page that lets the user edit title and description of an {@link Image}. + * + * @author David ‘Bombe’ Roden + */ +public class EditImagePage extends SoneTemplatePage { + + /** + * Creates a new “edit image” page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public EditImagePage(Template template, WebInterface webInterface) { + super("editImage.html", template, "Page.EditImage.Title", webInterface, true); + } + + // + // SONETEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override - protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException { ++ protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + super.processTemplate(request, templateContext); + if (request.getMethod() == Method.POST) { + String imageId = request.getHttpRequest().getPartAsStringFailsafe("image", 36); + Image image = webInterface.getCore().getImage(imageId, false); + if (image == null) { + throw new RedirectException("invalid.html"); + } + if (!webInterface.getCore().isLocalSone(image.getSone())) { + throw new RedirectException("noPermission.html"); + } + String title = request.getHttpRequest().getPartAsStringFailsafe("title", 100).trim(); + String description = request.getHttpRequest().getPartAsStringFailsafe("description", 1024).trim(); + if (title.length() == 0) { + templateContext.set("titleMissing", true); + } + image.setTitle(title); + image.setDescription(description); - webInterface.getCore().saveSone(image.getSone()); ++ webInterface.getCore().touchConfiguration(); + throw new RedirectException("imageBrowser.html?image=" + image.getId()); + } + } + +} diff --cc src/main/java/net/pterodactylus/sone/web/GetImagePage.java index fdd72a8,0000000..29556c9 mode 100644,000000..100644 --- a/src/main/java/net/pterodactylus/sone/web/GetImagePage.java +++ b/src/main/java/net/pterodactylus/sone/web/GetImagePage.java @@@ -1,64 -1,0 +1,77 @@@ +/* + * Sone - GetImagePage.java - Copyright © 2011 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 . + */ + +package net.pterodactylus.sone.web; + ++import java.io.IOException; ++ +import net.pterodactylus.sone.data.TemporaryImage; - import net.pterodactylus.sone.web.page.Page; ++import net.pterodactylus.sone.web.page.FreenetRequest; ++import net.pterodactylus.util.web.Page; ++import net.pterodactylus.util.web.Response; + +/** + * Page that delivers a {@link TemporaryImage} to the browser. + * + * @author David ‘Bombe’ Roden + */ - public class GetImagePage implements Page { ++public class GetImagePage implements Page { + + /** The Sone web interface. */ + private final WebInterface webInterface; + + /** + * Creates a new “get image” page. + * + * @param webInterface + * The Sone web interface + */ + public GetImagePage(WebInterface webInterface) { + this.webInterface = webInterface; + } + + /** + * {@inheritDoc} + */ + @Override + public String getPath() { + return "getImage.html"; + } + + /** + * {@inheritDoc} + */ + @Override - public Response handleRequest(Request request) { ++ public boolean isPrefixPage() { ++ return false; ++ } ++ ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public Response handleRequest(FreenetRequest request, Response response) throws IOException { + String imageId = request.getHttpRequest().getParam("image"); + TemporaryImage temporaryImage = webInterface.getCore().getTemporaryImage(imageId); + if (temporaryImage == null) { - return new Response(404, "Not found.", "text/plain; charset=utf-8", ""); ++ return response.setStatusCode(404).setStatusText("Not found.").setContentType("text/html; charset=utf-8"); + } - return new Response(200, "OK", temporaryImage.getMimeType(), temporaryImage.getImageData()).setHeader("Content-Disposition", "attachment; filename=" + temporaryImage.getId() + "." + temporaryImage.getMimeType().substring(temporaryImage.getMimeType().lastIndexOf("/") + 1)); ++ String contentType= temporaryImage.getMimeType(); ++ return response.setStatusCode(200).setStatusText("OK").setContentType(contentType).addHeader("Content-Disposition", "attachment; filename=" + temporaryImage.getId() + "." + contentType.substring(contentType.lastIndexOf('/') + 1)).write(temporaryImage.getImageData()); + } + +} diff --cc src/main/java/net/pterodactylus/sone/web/ImageBrowserPage.java index 778b429,0000000..ed31283 mode 100644,000000..100644 --- a/src/main/java/net/pterodactylus/sone/web/ImageBrowserPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ImageBrowserPage.java @@@ -1,78 -1,0 +1,79 @@@ +/* + * Sone - ImageBrowserPage.java - Copyright © 2011 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 . + */ + +package net.pterodactylus.sone.web; + +import net.pterodactylus.sone.data.Album; +import net.pterodactylus.sone.data.Image; +import net.pterodactylus.sone.data.Sone; ++import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; + +/** + * The image browser page is the entry page for the image management. + * + * @author David ‘Bombe’ Roden + */ +public class ImageBrowserPage extends SoneTemplatePage { + + /** + * Creates a new image browser page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public ImageBrowserPage(Template template, WebInterface webInterface) { + super("imageBrowser.html", template, "Page.ImageBrowser.Title", webInterface, true); + } + + // + // SONETEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override - protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException { ++ protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + super.processTemplate(request, templateContext); + String albumId = request.getHttpRequest().getParam("album", null); + if (albumId != null) { + Album album = webInterface.getCore().getAlbum(albumId, false); + templateContext.set("albumRequested", true); + templateContext.set("album", album); + return; + } + String imageId = request.getHttpRequest().getParam("image", null); + if (imageId != null) { + Image image = webInterface.getCore().getImage(imageId, false); + templateContext.set("imageRequested", true); + templateContext.set("image", image); + return; + } + Sone sone = getCurrentSone(request.getToadletContext(), false); + String soneId = request.getHttpRequest().getParam("sone", null); + if (soneId != null) { + sone = webInterface.getCore().getSone(soneId, false); + } + templateContext.set("soneRequested", true); + templateContext.set("sone", sone); + } + +} diff --cc src/main/java/net/pterodactylus/sone/web/UploadImagePage.java index 4d98179,0000000..e0aa1e1 mode 100644,000000..100644 --- a/src/main/java/net/pterodactylus/sone/web/UploadImagePage.java +++ b/src/main/java/net/pterodactylus/sone/web/UploadImagePage.java @@@ -1,160 -1,0 +1,161 @@@ +/* + * Sone - UploadImagePage.java - Copyright © 2011 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 . + */ + +package net.pterodactylus.sone.web; + +import java.awt.Image; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Iterator; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.stream.ImageInputStream; + +import net.pterodactylus.sone.data.Album; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.data.TemporaryImage; - import net.pterodactylus.sone.web.page.Page.Request.Method; ++import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.io.Closer; +import net.pterodactylus.util.io.StreamCopier; +import net.pterodactylus.util.logging.Logging; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; ++import net.pterodactylus.util.web.Method; +import freenet.support.api.Bucket; +import freenet.support.api.HTTPUploadedFile; + +/** + * Page implementation that lets the user upload an image. + * + * @author David ‘Bombe’ Roden + */ +public class UploadImagePage extends SoneTemplatePage { + + /** The logger. */ + private static final Logger logger = Logging.getLogger(UploadImagePage.class); + + /** + * Creates a new “upload image” page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public UploadImagePage(Template template, WebInterface webInterface) { + super("uploadImage.html", template, "Page.UploadImage.Title", webInterface, true); + } + + // + // SONETEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override - protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException { ++ protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + super.processTemplate(request, templateContext); + if (request.getMethod() == Method.POST) { + Sone currentSone = getCurrentSone(request.getToadletContext()); + String parentId = request.getHttpRequest().getPartAsStringFailsafe("parent", 36); + Album parent = webInterface.getCore().getAlbum(parentId, false); + if (parent == null) { + /* TODO - signal error */ + return; + } + if (!currentSone.equals(parent.getSone())) { + /* TODO - signal error. */ + return; + } + String name = request.getHttpRequest().getPartAsStringFailsafe("title", 200); + String description = request.getHttpRequest().getPartAsStringFailsafe("description", 4000); + HTTPUploadedFile uploadedFile = request.getHttpRequest().getUploadedFile("image"); + Bucket fileBucket = uploadedFile.getData(); + InputStream imageInputStream = null; + ByteArrayOutputStream imageDataOutputStream = null; + net.pterodactylus.sone.data.Image image = null; + try { + imageInputStream = fileBucket.getInputStream(); + /* TODO - check length */ + imageDataOutputStream = new ByteArrayOutputStream((int) fileBucket.size()); + StreamCopier.copy(imageInputStream, imageDataOutputStream); + } catch (IOException ioe1) { + logger.log(Level.WARNING, "Could not read uploaded image!", ioe1); + return; + } finally { + fileBucket.free(); + Closer.close(imageInputStream); + Closer.close(imageDataOutputStream); + } + byte[] imageData = imageDataOutputStream.toByteArray(); + ByteArrayInputStream imageDataInputStream = null; + Image uploadedImage = null; + try { + imageDataInputStream = new ByteArrayInputStream(imageData); + uploadedImage = ImageIO.read(imageDataInputStream); + if (uploadedImage == null) { + templateContext.set("messages", webInterface.getL10n().getString("Page.UploadImage.Error.InvalidImage")); + return; + } + String mimeType = getMimeType(imageData); + TemporaryImage temporaryImage = webInterface.getCore().createTemporaryImage(mimeType, imageData); + image = webInterface.getCore().createImage(currentSone, parent, temporaryImage); + image.setTitle(name).setDescription(description).setWidth(uploadedImage.getWidth(null)).setHeight(uploadedImage.getHeight(null)); + } catch (IOException ioe1) { + logger.log(Level.WARNING, "Could not read uploaded image!", ioe1); + return; + } finally { + Closer.close(imageDataInputStream); + Closer.flush(uploadedImage); + } + throw new RedirectException("imageBrowser.html?album=" + parent.getId()); + } + } + + // + // PRIVATE METHODS + // + + /** + * Tries to detect the MIME type of the encoded image. + * + * @param imageData + * The encoded image + * @return The MIME type of the image, or “application/octet-stream” if the + * image type could not be detected + */ + private String getMimeType(byte[] imageData) { + ByteArrayInputStream imageDataInputStream = new ByteArrayInputStream(imageData); + try { + ImageInputStream imageInputStream = ImageIO.createImageInputStream(imageDataInputStream); + Iterator imageReaders = ImageIO.getImageReaders(imageInputStream); + if (imageReaders.hasNext()) { + return imageReaders.next().getOriginatingProvider().getMIMETypes()[0]; + } + } catch (IOException ioe1) { + logger.log(Level.FINE, "Could not detect MIME type for image.", ioe1); + } + return "application/octet-stream"; + } + +} diff --cc src/main/java/net/pterodactylus/sone/web/WebInterface.java index f5b111f,fa6f52c..6ea8957 --- a/src/main/java/net/pterodactylus/sone/web/WebInterface.java +++ b/src/main/java/net/pterodactylus/sone/web/WebInterface.java @@@ -51,9 -49,7 +52,8 @@@ import net.pterodactylus.sone.template. import net.pterodactylus.sone.template.CssClassNameFilter; import net.pterodactylus.sone.template.HttpRequestAccessor; import net.pterodactylus.sone.template.IdentityAccessor; +import net.pterodactylus.sone.template.ImageLinkFilter; import net.pterodactylus.sone.template.JavascriptFilter; - import net.pterodactylus.sone.template.NotificationManagerAccessor; import net.pterodactylus.sone.template.ParserFilter; import net.pterodactylus.sone.template.PostAccessor; import net.pterodactylus.sone.template.ReplyAccessor; @@@ -121,10 -123,13 +127,13 @@@ import net.pterodactylus.util.template. import net.pterodactylus.util.template.XmlFilter; import net.pterodactylus.util.thread.Ticker; import net.pterodactylus.util.version.Version; + import net.pterodactylus.util.web.RedirectPage; + import net.pterodactylus.util.web.StaticPage; + import net.pterodactylus.util.web.TemplatePage; import freenet.clients.http.SessionManager; --import freenet.clients.http.SessionManager.Session; import freenet.clients.http.ToadletContainer; import freenet.clients.http.ToadletContext; ++import freenet.clients.http.SessionManager.Session; import freenet.l10n.BaseL10n; import freenet.support.api.HTTPRequest; @@@ -204,9 -210,7 +223,8 @@@ public class WebInterface implements Co templateContextFactory.addAccessor(Sone.class, new SoneAccessor(getCore())); templateContextFactory.addAccessor(Post.class, new PostAccessor(getCore())); templateContextFactory.addAccessor(Reply.class, new ReplyAccessor(getCore())); + templateContextFactory.addAccessor(Album.class, new AlbumAccessor()); templateContextFactory.addAccessor(Identity.class, new IdentityAccessor(getCore())); - templateContextFactory.addAccessor(NotificationManager.class, new NotificationManagerAccessor()); templateContextFactory.addAccessor(Trust.class, new TrustAccessor()); templateContextFactory.addAccessor(HTTPRequest.class, new HttpRequestAccessor()); templateContextFactory.addFilter("date", new DateFilter()); @@@ -224,10 -228,12 +242,13 @@@ templateContextFactory.addFilter("unknown", new UnknownDateFilter(getL10n(), "View.Sone.Text.UnknownDate")); templateContextFactory.addFilter("format", new FormatFilter()); templateContextFactory.addFilter("sort", new CollectionSortFilter()); + templateContextFactory.addFilter("image-link", new ImageLinkFilter(templateContextFactory)); templateContextFactory.addFilter("replyGroup", new ReplyGroupFilter()); + templateContextFactory.addFilter("in", new ContainsFilter()); + templateContextFactory.addFilter("unique", new UniqueElementFilter()); templateContextFactory.addProvider(Provider.TEMPLATE_CONTEXT_PROVIDER); templateContextFactory.addProvider(new ClassPathTemplateProvider()); + templateContextFactory.addTemplateObject("webInterface", this); templateContextFactory.addTemplateObject("formPassword", formPassword); /* create notifications. */ @@@ -560,12 -560,9 +584,13 @@@ Template deletePostTemplate = TemplateParser.parse(createReader("/templates/deletePost.html")); Template deleteReplyTemplate = TemplateParser.parse(createReader("/templates/deleteReply.html")); Template deleteSoneTemplate = TemplateParser.parse(createReader("/templates/deleteSone.html")); + Template imageBrowserTemplate = TemplateParser.parse(createReader("/templates/imageBrowser.html")); + Template createAlbumTemplate = TemplateParser.parse(createReader("/templates/createAlbum.html")); + Template deleteAlbumTemplate = TemplateParser.parse(createReader("/templates/deleteAlbum.html")); + Template deleteImageTemplate = TemplateParser.parse(createReader("/templates/deleteImage.html")); Template noPermissionTemplate = TemplateParser.parse(createReader("/templates/noPermission.html")); Template optionsTemplate = TemplateParser.parse(createReader("/templates/options.html")); + Template rescueTemplate = TemplateParser.parse(createReader("/templates/rescue.html")); Template aboutTemplate = TemplateParser.parse(createReader("/templates/about.html")); Template invalidTemplate = TemplateParser.parse(createReader("/templates/invalid.html")); Template postTemplate = TemplateParser.parse(createReader("/templates/include/viewPost.html")); @@@ -615,13 -606,13 +641,14 @@@ pageToadlets.add(pageToadletFactory.createPageToadlet(new SoneTemplatePage("noPermission.html", noPermissionTemplate, "Page.NoPermission.Title", this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new DismissNotificationPage(emptyTemplate, this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new SoneTemplatePage("invalid.html", invalidTemplate, "Page.Invalid.Title", this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new StaticPage("css/", "/static/css/", "text/css"))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new StaticPage("javascript/", "/static/javascript/", "text/javascript"))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new StaticPage("images/", "/static/images/", "image/png"))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new TemplatePage("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new StaticPage("css/", "/static/css/", "text/css"))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new StaticPage("javascript/", "/static/javascript/", "text/javascript"))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new StaticPage("images/", "/static/images/", "image/png"))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new TemplatePage("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new GetImagePage(this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new GetTranslationPage(this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new GetStatusAjaxPage(this))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new GetNotificationAjaxPage(this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new DismissNotificationAjaxPage(this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new CreatePostAjaxPage(this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new CreateReplyAjaxPage(this))); diff --cc src/main/resources/i18n/sone.en.properties index 5f0a9ee,739c615..51cbe9d --- a/src/main/resources/i18n/sone.en.properties +++ b/src/main/resources/i18n/sone.en.properties @@@ -136,8 -147,8 +149,9 @@@ Page.ViewSone.PostList.Title=Posts by { Page.ViewSone.PostList.Text.NoPostYet=This Sone has not yet posted anything. Page.ViewSone.Profile.Title=Profile Page.ViewSone.Profile.Label.Name=Name +Page.ViewSone.Profile.Label.Albums=Albums - Page.ViewSone.Replies.Title=Replies to Posts + Page.ViewSone.Profile.Name.WoTLink=web of trust profile + Page.ViewSone.Replies.Title=Posts {sone} has replied to Page.ViewPost.Title=View Post - Sone Page.ViewPost.Page.Title=View Post by {sone} @@@ -321,16 -300,8 +353,17 @@@ WebInterface.DefaultText.BirthMonth=Mon WebInterface.DefaultText.BirthYear=Year WebInterface.DefaultText.FieldName=Field name WebInterface.DefaultText.Option.InsertionDelay=Time to wait after a Sone is modified before insert (in seconds) +WebInterface.DefaultText.Search=What are you looking for? +WebInterface.DefaultText.CreateAlbum.Name=Album title +WebInterface.DefaultText.CreateAlbum.Description=Album description +WebInterface.DefaultText.EditAlbum.Title=Album title +WebInterface.DefaultText.EditAlbum.Description=Album description +WebInterface.DefaultText.UploadImage.Title=Image title +WebInterface.DefaultText.UploadImage.Description=Image description +WebInterface.DefaultText.EditImage.Title=Image title +WebInterface.DefaultText.EditImage.Description=Image description WebInterface.DefaultText.Option.PostsPerPage=Number of posts to show on a page + WebInterface.DefaultText.Option.CharactersPerPost=Number of characters per post after which to cut the post off WebInterface.DefaultText.Option.PositiveTrust=The positive trust to assign WebInterface.DefaultText.Option.NegativeTrust=The negative trust to assign WebInterface.DefaultText.Option.TrustComment=The comment to set in the web of trust @@@ -360,6 -332,6 +393,9 @@@ Notification.SoneRescued.Text=The follo Notification.SoneRescued.Text.RememberToUnlock=Please remember to control the posts and replies you have given and don’t forget to unlock your Sones! Notification.LockedSones.Text=The following Sones have been locked for more than 5 minutes. Please check if you really want to keep these Sones locked: Notification.NewVersion.Text=Version {version} of the Sone plugin was found. Download it from USK@nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI,DuQSUZiI~agF8c-6tjsFFGuZ8eICrzWCILB60nT8KKo,AQACAAE/sone/{edition}​! +Notification.InsertingImages.Text=The following images are being inserted: +Notification.InsertedImages.Text=The following images have been inserted: +Notification.ImageInsertFailed.Text=The following images could not be inserted: + Notification.Mention.ShortText=You have been mentioned. + Notification.Mention.Text=You have been mentioned in the following posts: + Notification.SoneInsert.Duration={0,number} {0,choice,0#seconds|1#second|1
<%= Page.ViewSone.Profile.Label.Name|l10n|html>
- + + <%foreach sone.albums album> + <%first> +
+
<%= Page.ViewSone.Profile.Label.Albums|l10n|html>
+
+ <%/first> + <%album.title|html><%notlast>, <%/notlast> + <%last> +
+
+ <%/last> + <%/foreach> + <%foreach sone.profile.fields field>
<% field.name|html>