├── war ├── WEB-INF │ ├── lib │ │ ├── gwt-servlet.jar │ │ ├── jdo2-api-2.3-eb.jar │ │ ├── jsr107cache-1.1.jar │ │ ├── datanucleus-core-1.1.5.jar │ │ ├── datanucleus-jpa-1.1.5.jar │ │ ├── appengine-api-labs-1.3.7.jar │ │ ├── appengine-api-1.0-sdk-1.3.7.jar │ │ ├── appengine-jsr107cache-1.3.7.jar │ │ ├── geronimo-jpa_3.0_spec-1.1.1.jar │ │ ├── geronimo-jta_1.1_spec-1.1.1.jar │ │ └── datanucleus-appengine-1.0.7.final.jar │ ├── logging.properties │ ├── appengine-web.xml │ └── web.xml ├── PhotoSharing.css └── PhotoSharing.html ├── .gitignore ├── test-classes └── com │ └── ikai │ └── photosharing │ └── server │ └── TagDaoTest.class ├── src ├── com │ └── ikai │ │ └── photosharing │ │ ├── client │ │ ├── events │ │ │ ├── GalleryUpdatedEventHandler.java │ │ │ └── GalleryUpdatedEvent.java │ │ ├── services │ │ │ ├── LoginServiceAsync.java │ │ │ ├── LoginService.java │ │ │ ├── UserImageService.java │ │ │ └── UserImageServiceAsync.java │ │ ├── widgets │ │ │ ├── PhotoGallery.ui.xml │ │ │ ├── UploadPhoto.ui.xml │ │ │ ├── ImageOverlay.ui.xml │ │ │ ├── PhotoGallery.java │ │ │ ├── UploadPhoto.java │ │ │ └── ImageOverlay.java │ │ └── Main.java │ │ ├── PhotoSharing.gwt.xml │ │ ├── server │ │ ├── LoginServiceImpl.java │ │ ├── UserImageServiceImpl.java │ │ ├── UploadedImageDao.java │ │ ├── TagDao.java │ │ └── UploadServlet.java │ │ └── shared │ │ ├── LoginInfo.java │ │ ├── UploadedImage.java │ │ ├── FieldVerifier.java │ │ └── Tag.java ├── META-INF │ └── jdoconfig.xml └── log4j.properties ├── README ├── LICENSE.txt └── test └── com └── ikai └── photosharing └── server └── TagDaoTest.java /war/WEB-INF/lib/gwt-servlet.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikai/gwt-gae-image-gallery/HEAD/war/WEB-INF/lib/gwt-servlet.jar -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | .settings 4 | war/WEB-INF/appengine-generated 5 | war/WEB-INF/classes 6 | war/photosharing 7 | -------------------------------------------------------------------------------- /war/WEB-INF/lib/jdo2-api-2.3-eb.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikai/gwt-gae-image-gallery/HEAD/war/WEB-INF/lib/jdo2-api-2.3-eb.jar -------------------------------------------------------------------------------- /war/WEB-INF/lib/jsr107cache-1.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikai/gwt-gae-image-gallery/HEAD/war/WEB-INF/lib/jsr107cache-1.1.jar -------------------------------------------------------------------------------- /war/WEB-INF/lib/datanucleus-core-1.1.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikai/gwt-gae-image-gallery/HEAD/war/WEB-INF/lib/datanucleus-core-1.1.5.jar -------------------------------------------------------------------------------- /war/WEB-INF/lib/datanucleus-jpa-1.1.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikai/gwt-gae-image-gallery/HEAD/war/WEB-INF/lib/datanucleus-jpa-1.1.5.jar -------------------------------------------------------------------------------- /war/WEB-INF/lib/appengine-api-labs-1.3.7.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikai/gwt-gae-image-gallery/HEAD/war/WEB-INF/lib/appengine-api-labs-1.3.7.jar -------------------------------------------------------------------------------- /war/WEB-INF/lib/appengine-api-1.0-sdk-1.3.7.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikai/gwt-gae-image-gallery/HEAD/war/WEB-INF/lib/appengine-api-1.0-sdk-1.3.7.jar -------------------------------------------------------------------------------- /war/WEB-INF/lib/appengine-jsr107cache-1.3.7.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikai/gwt-gae-image-gallery/HEAD/war/WEB-INF/lib/appengine-jsr107cache-1.3.7.jar -------------------------------------------------------------------------------- /war/WEB-INF/lib/geronimo-jpa_3.0_spec-1.1.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikai/gwt-gae-image-gallery/HEAD/war/WEB-INF/lib/geronimo-jpa_3.0_spec-1.1.1.jar -------------------------------------------------------------------------------- /war/WEB-INF/lib/geronimo-jta_1.1_spec-1.1.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikai/gwt-gae-image-gallery/HEAD/war/WEB-INF/lib/geronimo-jta_1.1_spec-1.1.1.jar -------------------------------------------------------------------------------- /war/WEB-INF/lib/datanucleus-appengine-1.0.7.final.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikai/gwt-gae-image-gallery/HEAD/war/WEB-INF/lib/datanucleus-appengine-1.0.7.final.jar -------------------------------------------------------------------------------- /test-classes/com/ikai/photosharing/server/TagDaoTest.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikai/gwt-gae-image-gallery/HEAD/test-classes/com/ikai/photosharing/server/TagDaoTest.class -------------------------------------------------------------------------------- /src/com/ikai/photosharing/client/events/GalleryUpdatedEventHandler.java: -------------------------------------------------------------------------------- 1 | package com.ikai.photosharing.client.events; 2 | 3 | import com.google.gwt.event.shared.EventHandler; 4 | 5 | public interface GalleryUpdatedEventHandler extends EventHandler { 6 | 7 | void onGalleryUpdated(GalleryUpdatedEvent event); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/com/ikai/photosharing/client/services/LoginServiceAsync.java: -------------------------------------------------------------------------------- 1 | package com.ikai.photosharing.client.services; 2 | 3 | import com.google.gwt.user.client.rpc.AsyncCallback; 4 | import com.ikai.photosharing.shared.LoginInfo; 5 | 6 | public interface LoginServiceAsync { 7 | public void login(String requestUri, AsyncCallback async); 8 | } -------------------------------------------------------------------------------- /src/com/ikai/photosharing/client/widgets/PhotoGallery.ui.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | .important { 6 | font-weight: bold; 7 | } 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/com/ikai/photosharing/client/services/LoginService.java: -------------------------------------------------------------------------------- 1 | package com.ikai.photosharing.client.services; 2 | 3 | import com.google.gwt.user.client.rpc.RemoteService; 4 | import com.google.gwt.user.client.rpc.RemoteServiceRelativePath; 5 | import com.ikai.photosharing.shared.LoginInfo; 6 | 7 | @RemoteServiceRelativePath("login") 8 | public interface LoginService extends RemoteService { 9 | public LoginInfo login(String requestUri); 10 | } -------------------------------------------------------------------------------- /src/com/ikai/photosharing/client/widgets/UploadPhoto.ui.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /war/WEB-INF/logging.properties: -------------------------------------------------------------------------------- 1 | # A default java.util.logging configuration. 2 | # (All App Engine logging is through java.util.logging by default). 3 | # 4 | # To use this configuration, copy it into your application's WEB-INF 5 | # folder and add the following to your appengine-web.xml: 6 | # 7 | # 8 | # 9 | # 10 | # 11 | 12 | # Set the default logging level for all loggers to WARNING 13 | .level = INFO 14 | -------------------------------------------------------------------------------- /src/com/ikai/photosharing/client/widgets/ImageOverlay.ui.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | .important { 6 | font-weight: bold; 7 | } 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /war/PhotoSharing.css: -------------------------------------------------------------------------------- 1 | /** Add css rules here for your application. */ 2 | 3 | 4 | /** Example rules used by the template application (remove for your app) */ 5 | h1 { 6 | font-size: 2em; 7 | font-weight: bold; 8 | color: #777777; 9 | margin: 40px 0px 70px; 10 | text-align: center; 11 | } 12 | 13 | .sendButton { 14 | display: block; 15 | font-size: 16pt; 16 | } 17 | 18 | /** Most GWT widgets already have a style name defined */ 19 | .gwt-DialogBox { 20 | width: 400px; 21 | } 22 | 23 | .dialogVPanel { 24 | margin: 5px; 25 | } 26 | 27 | .serverResponseLabelError { 28 | color: red; 29 | } 30 | 31 | /** Set ids using widget.getElement().setId("idOfElement") */ 32 | #closeButton { 33 | margin: 15px 6px 6px; 34 | } 35 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | This is a demo of using Google Web Toolkit, Google App Engine and GAE's high performance image serving library to build 2 | a basic image gallery application. Read more about this API here: 3 | 4 | http://code.google.com/appengine/docs/java/javadoc/com/google/appengine/api/images/ImagesService.html#getServingUrl(com.google.appengine.api.blobstore.BlobKey, int, boolean) 5 | 6 | Note that to deploy this, you will require your GAE application to be billing-enabled. This app was built using GWT 2.0.4 and GAE SDK 1.3.7. 7 | 8 | Testing 9 | -------- 10 | 11 | To test this application, you will need to add the requisite JARs to your build path. See this page for the required classes: 12 | 13 | http://code.google.com/appengine/docs/java/tools/localunittesting.html 14 | -------------------------------------------------------------------------------- /src/com/ikai/photosharing/client/services/UserImageService.java: -------------------------------------------------------------------------------- 1 | package com.ikai.photosharing.client.services; 2 | 3 | import java.util.List; 4 | 5 | import com.google.gwt.user.client.rpc.RemoteService; 6 | import com.google.gwt.user.client.rpc.RemoteServiceRelativePath; 7 | import com.ikai.photosharing.shared.Tag; 8 | import com.ikai.photosharing.shared.UploadedImage; 9 | 10 | @RemoteServiceRelativePath("images") 11 | public interface UserImageService extends RemoteService { 12 | 13 | public String getBlobstoreUploadUrl(); 14 | public UploadedImage get(String key); 15 | public List getRecentlyUploaded(); 16 | public void deleteImage(String key); 17 | public String tagImage(Tag tag); 18 | public List getTagsForImage(UploadedImage image); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /war/WEB-INF/appengine-web.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ikai-photoshare 4 | 1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/com/ikai/photosharing/client/services/UserImageServiceAsync.java: -------------------------------------------------------------------------------- 1 | package com.ikai.photosharing.client.services; 2 | 3 | import java.util.List; 4 | 5 | import com.google.gwt.user.client.rpc.AsyncCallback; 6 | import com.ikai.photosharing.shared.Tag; 7 | import com.ikai.photosharing.shared.UploadedImage; 8 | 9 | public interface UserImageServiceAsync { 10 | 11 | public void getBlobstoreUploadUrl(AsyncCallback callback); 12 | 13 | void get(String key, AsyncCallback callback); 14 | 15 | void getRecentlyUploaded(AsyncCallback> callback); 16 | 17 | void deleteImage(String key, AsyncCallback callback); 18 | 19 | void tagImage(Tag tag, 20 | AsyncCallback callback); 21 | 22 | void getTagsForImage(UploadedImage image, AsyncCallback> callback); 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/com/ikai/photosharing/client/events/GalleryUpdatedEvent.java: -------------------------------------------------------------------------------- 1 | package com.ikai.photosharing.client.events; 2 | 3 | import com.google.gwt.event.shared.GwtEvent; 4 | 5 | /** 6 | * @author Ikai Lan 7 | * 8 | * Represents a GalleryUpdatedEvent to fire to the message bus for all listeners. 9 | * 10 | * Inspiration: 11 | * http://stackoverflow.com/questions/2951621/gwt-custom-events 12 | * 13 | */ 14 | public class GalleryUpdatedEvent extends GwtEvent { 15 | 16 | public static Type TYPE = new Type(); 17 | 18 | @Override 19 | public Type getAssociatedType() { 20 | return TYPE; 21 | } 22 | 23 | @Override 24 | protected void dispatch(GalleryUpdatedEventHandler handler) { 25 | handler.onGalleryUpdated(this); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/META-INF/jdoconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/com/ikai/photosharing/PhotoSharing.gwt.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Ikai Lan 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /src/log4j.properties: -------------------------------------------------------------------------------- 1 | # A default log4j configuration for log4j users. 2 | # 3 | # To use this configuration, deploy it into your application's WEB-INF/classes 4 | # directory. You are also encouraged to edit it as you like. 5 | 6 | # Configure the console as our one appender 7 | log4j.appender.A1=org.apache.log4j.ConsoleAppender 8 | log4j.appender.A1.layout=org.apache.log4j.PatternLayout 9 | log4j.appender.A1.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] - %m%n 10 | 11 | # tighten logging on the DataNucleus Categories 12 | log4j.category.DataNucleus.JDO=WARN, A1 13 | log4j.category.DataNucleus.Persistence=WARN, A1 14 | log4j.category.DataNucleus.Cache=WARN, A1 15 | log4j.category.DataNucleus.MetaData=WARN, A1 16 | log4j.category.DataNucleus.General=WARN, A1 17 | log4j.category.DataNucleus.Utility=WARN, A1 18 | log4j.category.DataNucleus.Transaction=WARN, A1 19 | log4j.category.DataNucleus.Datastore=WARN, A1 20 | log4j.category.DataNucleus.ClassLoading=WARN, A1 21 | log4j.category.DataNucleus.Plugin=WARN, A1 22 | log4j.category.DataNucleus.ValueGeneration=WARN, A1 23 | log4j.category.DataNucleus.Enhancer=WARN, A1 24 | log4j.category.DataNucleus.SchemaTool=WARN, A1 25 | -------------------------------------------------------------------------------- /src/com/ikai/photosharing/server/LoginServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.ikai.photosharing.server; 2 | 3 | import com.google.appengine.api.users.User; 4 | import com.google.appengine.api.users.UserService; 5 | import com.google.appengine.api.users.UserServiceFactory; 6 | import com.google.gwt.user.server.rpc.RemoteServiceServlet; 7 | import com.ikai.photosharing.client.services.LoginService; 8 | import com.ikai.photosharing.shared.LoginInfo; 9 | 10 | @SuppressWarnings("serial") 11 | public class LoginServiceImpl extends RemoteServiceServlet implements 12 | LoginService { 13 | 14 | public LoginInfo login(String requestUri) { 15 | UserService userService = UserServiceFactory.getUserService(); 16 | User user = userService.getCurrentUser(); 17 | LoginInfo loginInfo = new LoginInfo(); 18 | 19 | if (user != null) { 20 | loginInfo.setLoggedIn(true); 21 | loginInfo.setEmailAddress(user.getEmail()); 22 | loginInfo.setNickname(user.getNickname()); 23 | loginInfo.setLogoutUrl(userService.createLogoutURL(requestUri)); 24 | loginInfo.setId(user.getUserId()); 25 | } else { 26 | loginInfo.setLoggedIn(false); 27 | loginInfo.setLoginUrl(userService.createLoginURL(requestUri)); 28 | } 29 | return loginInfo; 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/com/ikai/photosharing/shared/LoginInfo.java: -------------------------------------------------------------------------------- 1 | package com.ikai.photosharing.shared; 2 | 3 | import java.io.Serializable; 4 | 5 | @SuppressWarnings("serial") 6 | public class LoginInfo implements Serializable { 7 | 8 | private boolean loggedIn = false; 9 | private String loginUrl; 10 | private String logoutUrl; 11 | private String emailAddress; 12 | private String nickname; 13 | private String id; 14 | 15 | public boolean isLoggedIn() { 16 | return loggedIn; 17 | } 18 | 19 | public String getId() { 20 | return id; 21 | } 22 | 23 | public void setId(String id) { 24 | this.id = id; 25 | } 26 | 27 | public void setLoggedIn(boolean loggedIn) { 28 | this.loggedIn = loggedIn; 29 | } 30 | 31 | public String getLoginUrl() { 32 | return loginUrl; 33 | } 34 | 35 | public void setLoginUrl(String loginUrl) { 36 | this.loginUrl = loginUrl; 37 | } 38 | 39 | public String getLogoutUrl() { 40 | return logoutUrl; 41 | } 42 | 43 | public void setLogoutUrl(String logoutUrl) { 44 | this.logoutUrl = logoutUrl; 45 | } 46 | 47 | public String getEmailAddress() { 48 | return emailAddress; 49 | } 50 | 51 | public void setEmailAddress(String emailAddress) { 52 | this.emailAddress = emailAddress; 53 | } 54 | 55 | public String getNickname() { 56 | return nickname; 57 | } 58 | 59 | public void setNickname(String nickname) { 60 | this.nickname = nickname; 61 | } 62 | } -------------------------------------------------------------------------------- /war/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | userImageServlet 11 | com.ikai.photosharing.server.UserImageServiceImpl 12 | 13 | 14 | 15 | uploadServlet 16 | com.ikai.photosharing.server.UploadServlet 17 | 18 | 19 | 20 | loginServlet 21 | com.ikai.photosharing.server.LoginServiceImpl 22 | 23 | 24 | 25 | 26 | userImageServlet 27 | /photosharing/images 28 | 29 | 30 | 31 | uploadServlet 32 | /upload 33 | 34 | 35 | 36 | loginServlet 37 | /photosharing/login 38 | 39 | 40 | 41 | 42 | PhotoSharing.html 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/com/ikai/photosharing/shared/UploadedImage.java: -------------------------------------------------------------------------------- 1 | package com.ikai.photosharing.shared; 2 | 3 | import java.io.Serializable; 4 | import java.util.Date; 5 | import java.util.List; 6 | 7 | @SuppressWarnings("serial") 8 | public class UploadedImage implements Serializable { 9 | 10 | public static final String SERVING_URL = "servingUrl"; 11 | public static final String CREATED_AT = "createdAt"; 12 | public static final String OWNER_ID = "ownerId"; 13 | 14 | String key; 15 | String servingUrl; 16 | Date createdAt; 17 | String ownerId; // Refers to the User that uploaded this 18 | 19 | List tags; 20 | 21 | public String getKey() { 22 | return key; 23 | } 24 | 25 | public void setKey(String key) { 26 | this.key = key; 27 | } 28 | 29 | public String getServingUrl() { 30 | return servingUrl; 31 | } 32 | 33 | public void setServingUrl(String servingUrl) { 34 | this.servingUrl = servingUrl; 35 | } 36 | 37 | public Date getCreatedAt() { 38 | return createdAt; 39 | } 40 | 41 | public void setCreatedAt(Date createdAt) { 42 | this.createdAt = createdAt; 43 | } 44 | 45 | public String getOwnerId() { 46 | return ownerId; 47 | } 48 | 49 | public void setOwnerId(String ownerId) { 50 | this.ownerId = ownerId; 51 | } 52 | 53 | public List getTags() { 54 | return tags; 55 | } 56 | 57 | public void setTags(List tags) { 58 | this.tags = tags; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/com/ikai/photosharing/shared/FieldVerifier.java: -------------------------------------------------------------------------------- 1 | package com.ikai.photosharing.shared; 2 | 3 | /** 4 | *

5 | * FieldVerifier validates that the name the user enters is valid. 6 | *

7 | *

8 | * This class is in the shared packing because we use it in both 9 | * the client code and on the server. On the client, we verify that the name is 10 | * valid before sending an RPC request so the user doesn't have to wait for a 11 | * network round trip to get feedback. On the server, we verify that the name is 12 | * correct to ensure that the input is correct regardless of where the RPC 13 | * originates. 14 | *

15 | *

16 | * When creating a class that is used on both the client and the server, be sure 17 | * that all code is translatable and does not use native JavaScript. Code that 18 | * is note translatable (such as code that interacts with a database or the file 19 | * system) cannot be compiled into client side JavaScript. Code that uses native 20 | * JavaScript (such as Widgets) cannot be run on the server. 21 | *

22 | */ 23 | public class FieldVerifier { 24 | 25 | /** 26 | * Verifies that the specified name is valid for our service. 27 | * 28 | * In this example, we only require that the name is at least four 29 | * characters. In your application, you can use more complex checks to ensure 30 | * that usernames, passwords, email addresses, URLs, and other fields have the 31 | * proper syntax. 32 | * 33 | * @param name the name to validate 34 | * @return true if valid, false if invalid 35 | */ 36 | public static boolean isValidName(String name) { 37 | if (name == null) { 38 | return false; 39 | } 40 | return name.length() > 3; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/com/ikai/photosharing/server/UserImageServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.ikai.photosharing.server; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | 6 | import com.google.appengine.api.blobstore.BlobstoreService; 7 | import com.google.appengine.api.blobstore.BlobstoreServiceFactory; 8 | import com.google.appengine.api.users.User; 9 | import com.google.appengine.api.users.UserService; 10 | import com.google.appengine.api.users.UserServiceFactory; 11 | import com.google.gwt.user.server.rpc.RemoteServiceServlet; 12 | import com.ikai.photosharing.client.services.UserImageService; 13 | import com.ikai.photosharing.shared.Tag; 14 | import com.ikai.photosharing.shared.UploadedImage; 15 | 16 | @SuppressWarnings("serial") 17 | public class UserImageServiceImpl extends RemoteServiceServlet implements 18 | UserImageService { 19 | 20 | @Override 21 | public String getBlobstoreUploadUrl() { 22 | BlobstoreService blobstoreService = BlobstoreServiceFactory 23 | .getBlobstoreService(); 24 | return blobstoreService.createUploadUrl("/upload"); 25 | } 26 | 27 | @Override 28 | public UploadedImage get(String key) { 29 | UploadedImageDao dao = new UploadedImageDao(); 30 | UploadedImage image = dao.get(key); 31 | return image; 32 | } 33 | 34 | @Override 35 | public List getRecentlyUploaded() { 36 | UploadedImageDao dao = new UploadedImageDao(); 37 | List images = dao.getRecent(); 38 | return images; 39 | } 40 | 41 | @Override 42 | public void deleteImage(String key) { 43 | UserService userService = UserServiceFactory.getUserService(); 44 | User user = userService.getCurrentUser(); 45 | UploadedImageDao dao = new UploadedImageDao(); 46 | UploadedImage image = dao.get(key); 47 | if(image.getOwnerId().equals(user.getUserId())) { 48 | dao.delete(key); 49 | } 50 | } 51 | 52 | public String tagImage(Tag tag) { 53 | UserService userService = UserServiceFactory.getUserService(); 54 | User user = userService.getCurrentUser(); 55 | TagDao dao = new TagDao(); 56 | 57 | // TODO: Do validation here of x, y, ImageId 58 | 59 | tag.setTaggerId(user.getUserId()); 60 | tag.setCreatedAt(new Date()); 61 | 62 | String key = dao.put(tag); 63 | return key; 64 | } 65 | 66 | @Override 67 | public List getTagsForImage(UploadedImage image) { 68 | TagDao dao = new TagDao(); 69 | List tags = dao.getForImage(image); 70 | return tags; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/com/ikai/photosharing/server/UploadedImageDao.java: -------------------------------------------------------------------------------- 1 | package com.ikai.photosharing.server; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Date; 5 | import java.util.List; 6 | 7 | import com.google.appengine.api.datastore.DatastoreService; 8 | import com.google.appengine.api.datastore.DatastoreServiceFactory; 9 | import com.google.appengine.api.datastore.Entity; 10 | import com.google.appengine.api.datastore.EntityNotFoundException; 11 | import com.google.appengine.api.datastore.FetchOptions; 12 | import com.google.appengine.api.datastore.Key; 13 | import com.google.appengine.api.datastore.KeyFactory; 14 | import com.google.appengine.api.datastore.Query; 15 | import com.google.appengine.api.datastore.Query.SortDirection; 16 | import com.ikai.photosharing.shared.UploadedImage; 17 | 18 | public class UploadedImageDao { 19 | 20 | DatastoreService datastore; 21 | 22 | public UploadedImageDao() { 23 | datastore = DatastoreServiceFactory.getDatastoreService(); 24 | } 25 | 26 | public UploadedImage get(String encodedKey) { 27 | Key key = KeyFactory.stringToKey(encodedKey); 28 | try { 29 | Entity result = datastore.get(key); 30 | UploadedImage image = fromEntity(result); 31 | image.setKey(encodedKey); 32 | return image; 33 | } catch (EntityNotFoundException e) { 34 | return null; 35 | } 36 | 37 | } 38 | 39 | public List getRecent() { 40 | Query query = new Query("UploadedImage"); 41 | query.addSort(UploadedImage.CREATED_AT, SortDirection.DESCENDING); 42 | FetchOptions options = FetchOptions.Builder.withLimit(25); 43 | 44 | ArrayList results = new ArrayList(); 45 | for (Entity result : datastore.prepare(query).asIterable(options)) { 46 | UploadedImage image = fromEntity(result); 47 | results.add(image); 48 | } 49 | return results; 50 | } 51 | 52 | public void delete(String encodedKey) { 53 | Key key = KeyFactory.stringToKey(encodedKey); 54 | datastore.delete(key); 55 | } 56 | 57 | private UploadedImage fromEntity(Entity result) { 58 | UploadedImage image = new UploadedImage(); 59 | image.setCreatedAt((Date) result.getProperty(UploadedImage.CREATED_AT)); 60 | image.setServingUrl((String) result 61 | .getProperty(UploadedImage.SERVING_URL)); 62 | 63 | image.setOwnerId((String) result.getProperty(UploadedImage.OWNER_ID)); 64 | 65 | if (image.getKey() == null) { 66 | String encodedKey = KeyFactory.keyToString(result.getKey()); 67 | image.setKey(encodedKey); 68 | } 69 | 70 | return image; 71 | } 72 | 73 | 74 | 75 | } 76 | -------------------------------------------------------------------------------- /war/PhotoSharing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Web Application Starter Project 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 47 | 48 |

Photo Sharing

49 | 50 |
51 |
52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/com/ikai/photosharing/server/TagDao.java: -------------------------------------------------------------------------------- 1 | package com.ikai.photosharing.server; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Date; 5 | import java.util.List; 6 | 7 | import com.google.appengine.api.datastore.DatastoreService; 8 | import com.google.appengine.api.datastore.DatastoreServiceFactory; 9 | import com.google.appengine.api.datastore.Entity; 10 | import com.google.appengine.api.datastore.FetchOptions; 11 | import com.google.appengine.api.datastore.Key; 12 | import com.google.appengine.api.datastore.KeyFactory; 13 | import com.google.appengine.api.datastore.Query; 14 | import com.google.appengine.api.datastore.Query.FilterOperator; 15 | import com.ikai.photosharing.shared.Tag; 16 | import com.ikai.photosharing.shared.UploadedImage; 17 | 18 | public class TagDao { 19 | 20 | DatastoreService datastore; 21 | 22 | public TagDao() { 23 | datastore = DatastoreServiceFactory.getDatastoreService(); 24 | } 25 | 26 | 27 | /** 28 | * @param tag The Tag object we want persisted 29 | * @return a String encoded Key for the saved object 30 | */ 31 | public String put(Tag tag) { 32 | Entity tagEntity = new Entity(Tag.class.getSimpleName()); 33 | 34 | // TODO: Make this part of an Entity Group with UploadedImage as a parent 35 | tagEntity.setProperty("createdAt", tag.getCreatedAt()); 36 | tagEntity.setProperty("taggerId", tag.getTaggerId()); 37 | tagEntity.setProperty("photoKey", tag.getPhotoKey()); 38 | 39 | tagEntity.setUnindexedProperty("body", tag.getBody()); 40 | tagEntity.setUnindexedProperty("x", tag.getX()); 41 | tagEntity.setUnindexedProperty("y", tag.getY()); 42 | 43 | Key key = datastore.put(tagEntity); 44 | String encodedKey = KeyFactory.keyToString(key); 45 | return encodedKey; 46 | } 47 | 48 | public List getForImage(UploadedImage image) { 49 | Query query = new Query(Tag.class.getSimpleName()); 50 | query.addFilter("photoKey", FilterOperator.EQUAL, image.getKey()); 51 | 52 | List results = new ArrayList(); 53 | 54 | for(Entity entity : datastore.prepare(query).asIterable(FetchOptions.Builder.withDefaults())) { 55 | Tag tag = new Tag(); 56 | tag.setPhotoKey((String) entity.getProperty("photoKey")); 57 | tag.setBody((String) entity.getProperty("body")); 58 | tag.setCreatedAt((Date) entity.getProperty("createdAt")); 59 | tag.setTaggerId((String) entity.getProperty("taggerId")); 60 | tag.setX((Long) entity.getProperty("x")); 61 | tag.setY((Long) entity.getProperty("y")); 62 | results.add(tag); 63 | } 64 | 65 | return results; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/com/ikai/photosharing/client/Main.java: -------------------------------------------------------------------------------- 1 | package com.ikai.photosharing.client; 2 | 3 | import com.google.gwt.core.client.EntryPoint; 4 | import com.google.gwt.core.client.GWT; 5 | import com.google.gwt.user.client.rpc.AsyncCallback; 6 | import com.google.gwt.user.client.ui.Anchor; 7 | import com.google.gwt.user.client.ui.Label; 8 | import com.google.gwt.user.client.ui.RootPanel; 9 | import com.google.gwt.user.client.ui.VerticalPanel; 10 | import com.ikai.photosharing.client.services.LoginService; 11 | import com.ikai.photosharing.client.services.LoginServiceAsync; 12 | import com.ikai.photosharing.client.widgets.PhotoGallery; 13 | import com.ikai.photosharing.client.widgets.UploadPhoto; 14 | import com.ikai.photosharing.shared.LoginInfo; 15 | 16 | /** 17 | * Entry point classes define onModuleLoad(). 18 | */ 19 | public class Main implements EntryPoint { 20 | 21 | // Login code shamelessly stolen from: 22 | // http://code.google.com/webtoolkit/doc/latest/tutorial/appengine.html 23 | LoginServiceAsync loginService = GWT.create(LoginService.class); 24 | private LoginInfo loginInfo = null; 25 | 26 | private VerticalPanel loginPanel = new VerticalPanel(); 27 | private Label loginLabel = new Label("Sign in to upload images!"); 28 | private Anchor signInLink = new Anchor("Sign In"); 29 | 30 | private PhotoGallery galleryWidget; 31 | private UploadPhoto uploadWidget; 32 | 33 | public void onModuleLoad() { 34 | galleryWidget = new PhotoGallery(this); 35 | 36 | RootPanel.get("gallery").add(galleryWidget); 37 | 38 | loginService.login(GWT.getHostPageBaseURL(), 39 | new AsyncCallback() { 40 | 41 | @Override 42 | public void onSuccess(LoginInfo result) { 43 | loginInfo = result; 44 | if (loginInfo.isLoggedIn()) { 45 | uploadWidget = new UploadPhoto(result); 46 | 47 | // Bind it to event so uploadWidget can refresh the gallery 48 | uploadWidget.addGalleryUpdatedEventHandler(galleryWidget); 49 | 50 | RootPanel.get("photoSharing").add(uploadWidget); 51 | } else { 52 | loadLogin(); 53 | } 54 | 55 | } 56 | 57 | @Override 58 | public void onFailure(Throwable caught) { 59 | // TODO Auto-generated method stub 60 | 61 | } 62 | }); 63 | 64 | } 65 | 66 | // TODO: Turn login into a UiBinder 67 | private void loadLogin() { 68 | // Assemble login panel. 69 | signInLink.setHref(loginInfo.getLoginUrl()); 70 | loginPanel.add(loginLabel); 71 | loginPanel.add(signInLink); 72 | RootPanel.get("photoSharing").add(loginPanel); 73 | } 74 | 75 | public LoginInfo getLoginInfo() { 76 | return loginInfo; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /test/com/ikai/photosharing/server/TagDaoTest.java: -------------------------------------------------------------------------------- 1 | package com.ikai.photosharing.server; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import java.util.Date; 6 | import java.util.List; 7 | 8 | import org.junit.After; 9 | import org.junit.Assert; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.junit.matchers.JUnitMatchers; 13 | 14 | import com.google.appengine.api.datastore.DatastoreService; 15 | import com.google.appengine.api.datastore.DatastoreServiceFactory; 16 | import com.google.appengine.api.datastore.Entity; 17 | import com.google.appengine.api.datastore.EntityNotFoundException; 18 | import com.google.appengine.api.datastore.Key; 19 | import com.google.appengine.api.datastore.KeyFactory; 20 | import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; 21 | import com.google.appengine.tools.development.testing.LocalServiceTestHelper; 22 | import com.ikai.photosharing.shared.Tag; 23 | import com.ikai.photosharing.shared.UploadedImage; 24 | 25 | public class TagDaoTest { 26 | 27 | private final LocalServiceTestHelper helper = new LocalServiceTestHelper( 28 | new LocalDatastoreServiceTestConfig()); 29 | 30 | DatastoreService datastore; 31 | 32 | @Before 33 | public void setUp() { 34 | helper.setUp(); 35 | datastore = DatastoreServiceFactory.getDatastoreService(); 36 | 37 | } 38 | 39 | @After 40 | public void tearDown() { 41 | helper.tearDown(); 42 | } 43 | 44 | @Test 45 | public void testPut() { 46 | 47 | TagDao dao = new TagDao(); 48 | Tag tag = new Tag(); 49 | tag.setBody("tag body"); 50 | tag.setCreatedAt(new Date()); 51 | tag.setPhotoKey("photoKey"); 52 | tag.setTaggerId("1"); 53 | 54 | tag.setX(100); 55 | tag.setY(100); 56 | 57 | String key = dao.put(tag); 58 | 59 | Key rawKey = null; 60 | 61 | try { 62 | rawKey = KeyFactory.stringToKey(key); 63 | } catch (NullPointerException e) { 64 | Assert.fail("put() returned null key"); 65 | } 66 | 67 | try { 68 | Entity entity = datastore.get(rawKey); 69 | 70 | assertEquals("body not set", tag.getBody(), 71 | (String) entity.getProperty("body")); 72 | assertEquals("createdAt not set", tag.getCreatedAt(), 73 | (Date) entity.getProperty("createdAt")); 74 | assertEquals("taggerId not set", tag.getTaggerId(), 75 | (String) entity.getProperty("taggerId")); 76 | assertEquals("photoKey not set", tag.getPhotoKey(), 77 | (String) entity.getProperty("photoKey")); 78 | 79 | assertEquals("x not set", new Long(100l), 80 | (Long) entity.getProperty("x")); 81 | assertEquals("y not set", new Long(100l), 82 | (Long) entity.getProperty("y")); 83 | 84 | } catch (EntityNotFoundException e) { 85 | Assert.fail("Entity not saved correctly"); 86 | } 87 | 88 | } 89 | 90 | @Test 91 | public void testGetForPhoto() throws Exception { 92 | Key imageKey = KeyFactory.createKey("UploadedImage", "imageKey"); 93 | 94 | Tag tag = new Tag(); 95 | tag.setPhotoKey(KeyFactory.keyToString(imageKey)); 96 | tag.setX(0); 97 | tag.setY(0); 98 | 99 | TagDao dao = new TagDao(); 100 | dao.put(tag); 101 | 102 | UploadedImage image = new UploadedImage(); 103 | image.setKey(KeyFactory.keyToString(imageKey)); 104 | 105 | List results = dao.getForImage(image); 106 | Tag result = results.get(0); 107 | assertEquals(tag, result); 108 | 109 | 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/com/ikai/photosharing/server/UploadServlet.java: -------------------------------------------------------------------------------- 1 | package com.ikai.photosharing.server; 2 | 3 | import java.io.IOException; 4 | import java.util.Date; 5 | import java.util.Map; 6 | import java.util.logging.Logger; 7 | 8 | import javax.servlet.ServletException; 9 | import javax.servlet.http.HttpServlet; 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | 13 | import com.google.appengine.api.blobstore.BlobKey; 14 | import com.google.appengine.api.blobstore.BlobstoreService; 15 | import com.google.appengine.api.blobstore.BlobstoreServiceFactory; 16 | import com.google.appengine.api.datastore.DatastoreService; 17 | import com.google.appengine.api.datastore.DatastoreServiceFactory; 18 | import com.google.appengine.api.datastore.Entity; 19 | import com.google.appengine.api.datastore.KeyFactory; 20 | import com.google.appengine.api.images.ImagesService; 21 | import com.google.appengine.api.images.ImagesServiceFactory; 22 | import com.google.appengine.api.users.User; 23 | import com.google.appengine.api.users.UserService; 24 | import com.google.appengine.api.users.UserServiceFactory; 25 | import com.ikai.photosharing.shared.UploadedImage; 26 | 27 | @SuppressWarnings("serial") 28 | public class UploadServlet extends HttpServlet { 29 | private static final Logger log = Logger.getLogger(UploadServlet.class.getName()); 30 | 31 | 32 | private BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService(); 33 | 34 | public void doPost(HttpServletRequest req, HttpServletResponse res) 35 | throws ServletException, IOException { 36 | 37 | Map blobs = blobstoreService.getUploadedBlobs(req); 38 | BlobKey blobKey = blobs.get("image"); 39 | 40 | if (blobKey == null) { 41 | 42 | } else { 43 | 44 | ImagesService imagesService = ImagesServiceFactory.getImagesService(); 45 | String imageUrl = imagesService.getServingUrl(blobKey); 46 | 47 | UserService userService = UserServiceFactory.getUserService(); 48 | // TODO: Add a better check for whether the user is logged in or not 49 | // Don't even let the user upload or get here 50 | User user = userService.getCurrentUser(); 51 | 52 | Entity uploadedImage = new Entity("UploadedImage"); 53 | uploadedImage.setProperty("blobKey", blobKey); 54 | uploadedImage.setProperty(UploadedImage.CREATED_AT, new Date()); 55 | uploadedImage.setProperty(UploadedImage.OWNER_ID, user.getUserId()); 56 | 57 | // Highly unlikely we'll ever search on this property 58 | uploadedImage.setUnindexedProperty(UploadedImage.SERVING_URL, imageUrl); 59 | 60 | DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); 61 | datastore.put(uploadedImage); 62 | 63 | String keyString = KeyFactory.keyToString(uploadedImage.getKey()); 64 | res.sendRedirect("/upload?uploadedImageKey=" + keyString); 65 | } 66 | } 67 | 68 | @Override 69 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) 70 | throws ServletException, IOException { 71 | 72 | String uploadedImageKey = req.getParameter("uploadedImageKey"); 73 | resp.setHeader("Content-Type", "text/html"); 74 | 75 | // This is a bit hacky, but it'll work. We'll use this key in an Async service to 76 | // fetch the image and image information 77 | resp.getWriter().println(uploadedImageKey); 78 | 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/com/ikai/photosharing/shared/Tag.java: -------------------------------------------------------------------------------- 1 | package com.ikai.photosharing.shared; 2 | 3 | import java.io.Serializable; 4 | import java.util.Date; 5 | 6 | /** 7 | * 8 | * A single Tag for a given Photo. 9 | * 10 | * @author Ikai Lan 11 | * 12 | */ 13 | @SuppressWarnings("serial") 14 | public class Tag implements Serializable { 15 | 16 | String photoKey; 17 | 18 | /** 19 | * User ID of the User that created this tag 20 | */ 21 | String taggerId; 22 | 23 | String body; 24 | 25 | Date createdAt; 26 | 27 | Long x; 28 | Long y; 29 | 30 | public String getPhotoKey() { 31 | return photoKey; 32 | } 33 | 34 | public void setPhotoKey(String photoKey) { 35 | this.photoKey = photoKey; 36 | } 37 | 38 | public String getTaggerId() { 39 | return taggerId; 40 | } 41 | 42 | public void setTaggerId(String taggerId) { 43 | this.taggerId = taggerId; 44 | } 45 | 46 | public String getBody() { 47 | return body; 48 | } 49 | 50 | public void setBody(String body) { 51 | this.body = body; 52 | } 53 | 54 | public Date getCreatedAt() { 55 | return createdAt; 56 | } 57 | 58 | public void setCreatedAt(Date createdAt) { 59 | this.createdAt = createdAt; 60 | } 61 | 62 | public Long getX() { 63 | return x; 64 | } 65 | 66 | public void setX(Number x) { 67 | this.x = x.longValue(); 68 | } 69 | 70 | public Long getY() { 71 | return y; 72 | } 73 | 74 | public void setY(Number y) { 75 | this.y = y.longValue(); 76 | } 77 | 78 | /* (non-Javadoc) 79 | * @see java.lang.Object#hashCode() 80 | */ 81 | @Override 82 | public int hashCode() { 83 | final int prime = 31; 84 | int result = 1; 85 | result = prime * result + ((body == null) ? 0 : body.hashCode()); 86 | result = prime * result 87 | + ((createdAt == null) ? 0 : createdAt.hashCode()); 88 | result = prime * result 89 | + ((photoKey == null) ? 0 : photoKey.hashCode()); 90 | result = prime * result 91 | + ((taggerId == null) ? 0 : taggerId.hashCode()); 92 | result = prime * result + ((x == null) ? 0 : x.hashCode()); 93 | result = prime * result + ((y == null) ? 0 : y.hashCode()); 94 | return result; 95 | } 96 | 97 | @Override 98 | public boolean equals(Object obj) { 99 | if (this == obj) { 100 | return true; 101 | } 102 | if (obj == null) { 103 | return false; 104 | } 105 | if (getClass() != obj.getClass()) { 106 | return false; 107 | } 108 | Tag other = (Tag) obj; 109 | if (body == null) { 110 | if (other.body != null) { 111 | return false; 112 | } 113 | } else if (!body.equals(other.body)) { 114 | return false; 115 | } 116 | if (createdAt == null) { 117 | if (other.createdAt != null) { 118 | return false; 119 | } 120 | } else if (!createdAt.equals(other.createdAt)) { 121 | return false; 122 | } 123 | if (photoKey == null) { 124 | if (other.photoKey != null) { 125 | return false; 126 | } 127 | } else if (!photoKey.equals(other.photoKey)) { 128 | return false; 129 | } 130 | if (taggerId == null) { 131 | if (other.taggerId != null) { 132 | return false; 133 | } 134 | } else if (!taggerId.equals(other.taggerId)) { 135 | return false; 136 | } 137 | if (x == null) { 138 | if (other.x != null) { 139 | return false; 140 | } 141 | } else if (!x.equals(other.x)) { 142 | return false; 143 | } 144 | if (y == null) { 145 | if (other.y != null) { 146 | return false; 147 | } 148 | } else if (!y.equals(other.y)) { 149 | return false; 150 | } 151 | return true; 152 | } 153 | 154 | 155 | 156 | } 157 | -------------------------------------------------------------------------------- /src/com/ikai/photosharing/client/widgets/PhotoGallery.java: -------------------------------------------------------------------------------- 1 | package com.ikai.photosharing.client.widgets; 2 | 3 | import java.util.List; 4 | 5 | import com.google.gwt.core.client.GWT; 6 | import com.google.gwt.event.dom.client.ClickEvent; 7 | import com.google.gwt.event.dom.client.ClickHandler; 8 | import com.google.gwt.event.dom.client.MouseOutEvent; 9 | import com.google.gwt.event.dom.client.MouseOutHandler; 10 | import com.google.gwt.event.dom.client.MouseOverEvent; 11 | import com.google.gwt.event.dom.client.MouseOverHandler; 12 | import com.google.gwt.uibinder.client.UiBinder; 13 | import com.google.gwt.uibinder.client.UiField; 14 | import com.google.gwt.user.client.rpc.AsyncCallback; 15 | import com.google.gwt.user.client.ui.Composite; 16 | import com.google.gwt.user.client.ui.DecoratedPopupPanel; 17 | import com.google.gwt.user.client.ui.FlexTable; 18 | import com.google.gwt.user.client.ui.HTML; 19 | import com.google.gwt.user.client.ui.Image; 20 | import com.google.gwt.user.client.ui.PopupPanel; 21 | import com.google.gwt.user.client.ui.Widget; 22 | import com.ikai.photosharing.client.Main; 23 | import com.ikai.photosharing.client.events.GalleryUpdatedEvent; 24 | import com.ikai.photosharing.client.events.GalleryUpdatedEventHandler; 25 | import com.ikai.photosharing.client.services.UserImageService; 26 | import com.ikai.photosharing.client.services.UserImageServiceAsync; 27 | import com.ikai.photosharing.shared.UploadedImage; 28 | 29 | public class PhotoGallery extends Composite implements GalleryUpdatedEventHandler { 30 | 31 | private static PhotoGalleryUiBinder uiBinder = GWT 32 | .create(PhotoGalleryUiBinder.class); 33 | 34 | UserImageServiceAsync userImageService = GWT.create(UserImageService.class); 35 | 36 | interface PhotoGalleryUiBinder extends UiBinder { 37 | } 38 | 39 | private static final int GALLERY_WIDTH = 5; 40 | 41 | @UiField 42 | FlexTable galleryTable; 43 | 44 | Main parent; 45 | 46 | public PhotoGallery(Main parent) { 47 | this.parent = parent; 48 | 49 | initWidget(uiBinder.createAndBindUi(this)); 50 | refreshGallery(); 51 | } 52 | 53 | public void refreshGallery() { 54 | userImageService 55 | .getRecentlyUploaded(new AsyncCallback>() { 56 | 57 | @Override 58 | public void onSuccess(List images) { 59 | 60 | int currentColumn = 0; 61 | int currentRow = 0; 62 | for (final UploadedImage image : images) { 63 | 64 | Image imageWidget = createImageWidget(image); 65 | 66 | galleryTable.setWidget(currentRow, currentColumn, 67 | imageWidget); 68 | 69 | currentColumn++; 70 | if (currentColumn >= GALLERY_WIDTH) { 71 | currentColumn = 0; 72 | currentRow++; 73 | } 74 | } 75 | 76 | } 77 | 78 | @Override 79 | public void onFailure(Throwable caught) { 80 | // TODO Auto-generated method stub 81 | 82 | } 83 | }); 84 | } 85 | 86 | private Image createImageWidget(final UploadedImage image) { 87 | final Image imageWidget = new Image(); 88 | imageWidget.setUrl(image.getServingUrl() + "=s200"); 89 | final DecoratedPopupPanel simplePopup = new DecoratedPopupPanel(true); 90 | 91 | imageWidget.addMouseOverHandler(new MouseOverHandler() { 92 | 93 | @Override 94 | public void onMouseOver(MouseOverEvent event) { 95 | Widget source = (Widget) event.getSource(); 96 | int left = source.getAbsoluteLeft() + 10; 97 | int top = source.getAbsoluteTop() + source.getOffsetHeight() 98 | + 10; 99 | 100 | simplePopup.setWidth("150px"); 101 | simplePopup.setWidget(new HTML("Uploaded: " 102 | + image.getCreatedAt())); 103 | simplePopup.show(); 104 | simplePopup.setPopupPosition(left, top); 105 | } 106 | }); 107 | 108 | imageWidget.addMouseOutHandler(new MouseOutHandler() { 109 | 110 | @Override 111 | public void onMouseOut(MouseOutEvent event) { 112 | simplePopup.hide(); 113 | } 114 | }); 115 | 116 | imageWidget.addClickHandler(new ClickHandler() { 117 | 118 | @Override 119 | public void onClick(ClickEvent event) { 120 | ImageOverlay imageOverlay = new ImageOverlay(image, parent.getLoginInfo()); 121 | imageOverlay.addGalleryUpdatedEventHandler(PhotoGallery.this); 122 | 123 | final PopupPanel imagePopup = new PopupPanel(true); 124 | imagePopup.setAnimationEnabled(true); 125 | imagePopup.setWidget(imageOverlay); 126 | imagePopup.setGlassEnabled(true); 127 | imagePopup.setAutoHideEnabled(true); 128 | 129 | imagePopup.center(); 130 | } 131 | }); 132 | 133 | return imageWidget; 134 | } 135 | 136 | @Override 137 | public void onGalleryUpdated(GalleryUpdatedEvent event) { 138 | refreshGallery(); 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /src/com/ikai/photosharing/client/widgets/UploadPhoto.java: -------------------------------------------------------------------------------- 1 | package com.ikai.photosharing.client.widgets; 2 | 3 | import com.google.gwt.core.client.GWT; 4 | import com.google.gwt.event.dom.client.ClickEvent; 5 | import com.google.gwt.event.shared.GwtEvent; 6 | import com.google.gwt.event.shared.HandlerManager; 7 | import com.google.gwt.event.shared.HandlerRegistration; 8 | import com.google.gwt.event.shared.HasHandlers; 9 | import com.google.gwt.uibinder.client.UiBinder; 10 | import com.google.gwt.uibinder.client.UiField; 11 | import com.google.gwt.uibinder.client.UiHandler; 12 | import com.google.gwt.user.client.rpc.AsyncCallback; 13 | import com.google.gwt.user.client.ui.Button; 14 | import com.google.gwt.user.client.ui.Composite; 15 | import com.google.gwt.user.client.ui.FileUpload; 16 | import com.google.gwt.user.client.ui.FormPanel; 17 | import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteEvent; 18 | import com.google.gwt.user.client.ui.PopupPanel; 19 | import com.google.gwt.user.client.ui.Widget; 20 | import com.ikai.photosharing.client.events.GalleryUpdatedEvent; 21 | import com.ikai.photosharing.client.events.GalleryUpdatedEventHandler; 22 | import com.ikai.photosharing.client.services.UserImageService; 23 | import com.ikai.photosharing.client.services.UserImageServiceAsync; 24 | import com.ikai.photosharing.shared.LoginInfo; 25 | import com.ikai.photosharing.shared.UploadedImage; 26 | 27 | public class UploadPhoto extends Composite implements HasHandlers { 28 | 29 | private static UploadPhotoUiBinder uiBinder = GWT 30 | .create(UploadPhotoUiBinder.class); 31 | 32 | UserImageServiceAsync userImageService = GWT.create(UserImageService.class); 33 | 34 | private HandlerManager handlerManager; 35 | 36 | interface UploadPhotoUiBinder extends UiBinder { 37 | } 38 | 39 | @UiField 40 | Button uploadButton; 41 | 42 | @UiField 43 | FormPanel uploadForm; 44 | 45 | @UiField 46 | FileUpload uploadField; 47 | 48 | LoginInfo loginInfo; 49 | 50 | public UploadPhoto(final LoginInfo loginInfo) { 51 | handlerManager = new HandlerManager(this); 52 | 53 | this.loginInfo = loginInfo; 54 | 55 | initWidget(uiBinder.createAndBindUi(this)); 56 | 57 | uploadButton.setText("Upload"); 58 | uploadButton.setText("Loading..."); 59 | uploadButton.setEnabled(false); 60 | 61 | uploadField.setName("image"); 62 | 63 | startNewBlobstoreSession(); 64 | 65 | uploadForm 66 | .addSubmitCompleteHandler(new FormPanel.SubmitCompleteHandler() { 67 | 68 | @Override 69 | public void onSubmitComplete(SubmitCompleteEvent event) { 70 | uploadForm.reset(); 71 | startNewBlobstoreSession(); 72 | 73 | String key = event.getResults(); 74 | 75 | userImageService.get(key, 76 | new AsyncCallback() { 77 | 78 | @Override 79 | public void onFailure(Throwable caught) { 80 | // TODO Auto-generated method stub 81 | 82 | } 83 | 84 | @Override 85 | public void onSuccess(UploadedImage result) { 86 | 87 | ImageOverlay overlay = new ImageOverlay( 88 | result, loginInfo); 89 | GalleryUpdatedEvent event = new GalleryUpdatedEvent(); 90 | fireEvent(event); 91 | 92 | // TODO: Add something here that says, 93 | // hey, upload succeeded 94 | 95 | final PopupPanel imagePopup = new PopupPanel( 96 | true); 97 | imagePopup.setAnimationEnabled(true); 98 | imagePopup.setWidget(overlay); 99 | imagePopup.setGlassEnabled(true); 100 | imagePopup.setAutoHideEnabled(true); 101 | 102 | imagePopup.center(); 103 | 104 | } 105 | }); 106 | 107 | } 108 | }); 109 | } 110 | 111 | private void startNewBlobstoreSession() { 112 | userImageService.getBlobstoreUploadUrl(new AsyncCallback() { 113 | 114 | @Override 115 | public void onSuccess(String result) { 116 | uploadForm.setAction(result); 117 | uploadForm.setEncoding(FormPanel.ENCODING_MULTIPART); 118 | uploadForm.setMethod(FormPanel.METHOD_POST); 119 | 120 | uploadButton.setText("Upload"); 121 | uploadButton.setEnabled(true); 122 | 123 | } 124 | 125 | @Override 126 | public void onFailure(Throwable caught) { 127 | // TODO Auto-generated method stub 128 | 129 | } 130 | }); 131 | } 132 | 133 | @UiHandler("uploadButton") 134 | void onSubmit(ClickEvent e) { 135 | uploadForm.submit(); 136 | } 137 | 138 | @Override 139 | public void fireEvent(GwtEvent event) { 140 | handlerManager.fireEvent(event); 141 | } 142 | 143 | public HandlerRegistration addGalleryUpdatedEventHandler( 144 | GalleryUpdatedEventHandler handler) { 145 | return handlerManager.addHandler(GalleryUpdatedEvent.TYPE, handler); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/com/ikai/photosharing/client/widgets/ImageOverlay.java: -------------------------------------------------------------------------------- 1 | package com.ikai.photosharing.client.widgets; 2 | 3 | import java.util.List; 4 | 5 | import com.google.gwt.core.client.GWT; 6 | import com.google.gwt.dom.client.Element; 7 | import com.google.gwt.event.dom.client.ClickEvent; 8 | import com.google.gwt.event.dom.client.ClickHandler; 9 | import com.google.gwt.event.dom.client.KeyCodes; 10 | import com.google.gwt.event.dom.client.KeyPressEvent; 11 | import com.google.gwt.event.dom.client.KeyPressHandler; 12 | import com.google.gwt.event.dom.client.MouseDownEvent; 13 | import com.google.gwt.event.shared.GwtEvent; 14 | import com.google.gwt.event.shared.HandlerManager; 15 | import com.google.gwt.event.shared.HandlerRegistration; 16 | import com.google.gwt.event.shared.HasHandlers; 17 | import com.google.gwt.uibinder.client.UiBinder; 18 | import com.google.gwt.uibinder.client.UiField; 19 | import com.google.gwt.uibinder.client.UiHandler; 20 | import com.google.gwt.user.client.rpc.AsyncCallback; 21 | import com.google.gwt.user.client.ui.Button; 22 | import com.google.gwt.user.client.ui.Composite; 23 | import com.google.gwt.user.client.ui.DialogBox; 24 | import com.google.gwt.user.client.ui.HTMLPanel; 25 | import com.google.gwt.user.client.ui.Image; 26 | import com.google.gwt.user.client.ui.Label; 27 | import com.google.gwt.user.client.ui.TextBox; 28 | import com.google.gwt.user.client.ui.VerticalPanel; 29 | import com.google.gwt.user.client.ui.Widget; 30 | import com.ikai.photosharing.client.events.GalleryUpdatedEvent; 31 | import com.ikai.photosharing.client.events.GalleryUpdatedEventHandler; 32 | import com.ikai.photosharing.client.services.UserImageService; 33 | import com.ikai.photosharing.client.services.UserImageServiceAsync; 34 | import com.ikai.photosharing.shared.LoginInfo; 35 | import com.ikai.photosharing.shared.Tag; 36 | import com.ikai.photosharing.shared.UploadedImage; 37 | 38 | /** 39 | * 40 | * This class represents the ImageOverlay that pops up when a User clicks on an 41 | * Image. It also provides listeners for management, tagging, and other 42 | * functions which are considered "Menu" type functions for a given image. 43 | * 44 | * @author Ikai Lan 45 | * 46 | */ 47 | public class ImageOverlay extends Composite implements HasHandlers { 48 | 49 | private static ImageOverlayUiBinder uiBinder = GWT 50 | .create(ImageOverlayUiBinder.class); 51 | 52 | UserImageServiceAsync imageService = GWT.create(UserImageService.class); 53 | 54 | private HandlerManager handlerManager; 55 | 56 | interface ImageOverlayUiBinder extends UiBinder { 57 | } 58 | 59 | @UiField 60 | Button deleteButton; 61 | 62 | @UiField 63 | Image image; 64 | 65 | @UiField 66 | Label timestamp; 67 | 68 | @UiField 69 | VerticalPanel tagPanel; 70 | 71 | protected UploadedImage uploadedImage; 72 | LoginInfo loginInfo; 73 | 74 | public ImageOverlay(UploadedImage uploadedImage, LoginInfo loginInfo) { 75 | handlerManager = new HandlerManager(this); 76 | 77 | this.uploadedImage = uploadedImage; 78 | this.loginInfo = loginInfo; 79 | 80 | initWidget(uiBinder.createAndBindUi(this)); 81 | 82 | image.setUrl(uploadedImage.getServingUrl()); 83 | timestamp.setText("Created at:" + uploadedImage.getCreatedAt()); 84 | 85 | if (loginInfo != null 86 | && (loginInfo.getId().equals(uploadedImage.getOwnerId()))) { 87 | deleteButton.setText("Delete image"); 88 | deleteButton.setVisible(true); 89 | } else { 90 | deleteButton.setVisible(false); 91 | } 92 | 93 | // Now let's fetch the tags 94 | imageService.getTagsForImage(uploadedImage, 95 | new AsyncCallback>() { 96 | 97 | @Override 98 | public void onFailure(Throwable caught) { 99 | // TODO Auto-generated method stub 100 | 101 | } 102 | 103 | @Override 104 | public void onSuccess(List result) { 105 | // TODO Auto-generated method stub 106 | for (Tag tag : result) { 107 | tagPanel.add(new HTMLPanel(tag.getBody())); 108 | } 109 | 110 | } 111 | }); 112 | 113 | } 114 | 115 | @UiHandler("image") 116 | void onClickImage(MouseDownEvent e) { 117 | Element imageElement = e.getRelativeElement(); 118 | int x = e.getRelativeX(imageElement); 119 | int y = e.getRelativeY(imageElement); 120 | // Window.alert("X: " + x + " Y: "+ y); 121 | TagDialog tagDialog = new TagDialog(image, x, y); 122 | tagDialog.showAndFocus(); 123 | 124 | } 125 | 126 | /** 127 | * 128 | * Handles clicking of the delete button if owned. 129 | * 130 | * @param {{@link ClickEvent} e 131 | */ 132 | @UiHandler("deleteButton") 133 | void onClick(ClickEvent e) { 134 | final ImageOverlay overlay = this; 135 | imageService.deleteImage(uploadedImage.getKey(), 136 | new AsyncCallback() { 137 | 138 | @Override 139 | public void onSuccess(Void result) { 140 | GalleryUpdatedEvent event = new GalleryUpdatedEvent(); 141 | fireEvent(event); 142 | overlay.removeFromParent(); 143 | } 144 | 145 | @Override 146 | public void onFailure(Throwable caught) { 147 | // TODO Auto-generated method stub 148 | 149 | } 150 | }); 151 | 152 | } 153 | 154 | /** 155 | * @author Ikai Lan 156 | * 157 | * This is the dialog box to ask the user to tag the image 158 | */ 159 | private class TagDialog extends DialogBox { 160 | 161 | private TextBox textBox; 162 | 163 | public TagDialog(Image image, final int x, final int y) { 164 | 165 | // Set the dialog box's caption. 166 | setText("Tagging X: " + x + " Y: " + y); 167 | 168 | setAutoHideEnabled(true); 169 | 170 | int dialogX = image.getAbsoluteLeft() + x; 171 | int dialogY = image.getAbsoluteTop() + y; 172 | 173 | setPopupPosition(dialogX, dialogY); 174 | 175 | VerticalPanel tagPanel = new VerticalPanel(); 176 | 177 | textBox = new TextBox(); 178 | tagPanel.add(textBox); 179 | 180 | textBox.addKeyPressHandler(new KeyPressHandler() { 181 | 182 | @Override 183 | public void onKeyPress(KeyPressEvent event) { 184 | if (event.getCharCode() == KeyCodes.KEY_ENTER) { 185 | saveTag(x, y, textBox); 186 | } 187 | 188 | } 189 | }); 190 | 191 | Button ok = new Button("Tag"); 192 | ok.addClickHandler(new ClickHandler() { 193 | public void onClick(ClickEvent event) { 194 | saveTag(x, y, textBox); 195 | } 196 | }); 197 | 198 | tagPanel.add(ok); 199 | 200 | setWidget(tagPanel); 201 | 202 | } 203 | 204 | public void showAndFocus() { 205 | show(); 206 | textBox.setFocus(true); 207 | } 208 | 209 | private void saveTag(final int x, final int y, final TextBox textBox) { 210 | Tag tag = new Tag(); 211 | 212 | // TODO: Change this to also pass the Image 213 | tag.setPhotoKey(uploadedImage.getKey()); 214 | tag.setBody(textBox.getValue()); 215 | tag.setX(x); 216 | tag.setY(y); 217 | 218 | imageService.tagImage(tag, new AsyncCallback() { 219 | 220 | @Override 221 | public void onFailure(Throwable caught) { 222 | // TODO Auto-generated method stub 223 | 224 | } 225 | 226 | @Override 227 | public void onSuccess(String result) { 228 | TagDialog.this.hide(); 229 | } 230 | }); 231 | } 232 | } 233 | 234 | @Override 235 | public void fireEvent(GwtEvent event) { 236 | handlerManager.fireEvent(event); 237 | } 238 | 239 | public HandlerRegistration addGalleryUpdatedEventHandler( 240 | GalleryUpdatedEventHandler handler) { 241 | return handlerManager.addHandler(GalleryUpdatedEvent.TYPE, handler); 242 | } 243 | 244 | } 245 | --------------------------------------------------------------------------------