From 3cccf741d6da825ac259e64d928fa6d2dba6bc69 Mon Sep 17 00:00:00 2001 From: Athou Date: Tue, 28 Oct 2014 16:31:49 +0100 Subject: [PATCH 001/241] dropwizard upgrade to 0.8.0 --- CHANGELOG | 2 + pom.xml | 32 ++++- .../com/commafeed/CommaFeedApplication.java | 18 +-- .../frontend/auth/SecurityCheckFactory.java | 96 ++++++++++++++ .../auth/SecurityCheckFactoryProvider.java | 64 +++++++++ .../frontend/auth/SecurityCheckProvider.java | 124 ------------------ .../commafeed/frontend/resource/FeedREST.java | 2 +- .../session/SessionHelperFactory.java | 17 +++ .../session/SessionHelperFactoryProvider.java | 56 ++++++++ .../session/SessionHelperProvider.java | 44 ------- ...est.java => SecurityCheckFactoryTest.java} | 8 +- 11 files changed, 274 insertions(+), 189 deletions(-) create mode 100644 src/main/java/com/commafeed/frontend/auth/SecurityCheckFactory.java create mode 100644 src/main/java/com/commafeed/frontend/auth/SecurityCheckFactoryProvider.java delete mode 100644 src/main/java/com/commafeed/frontend/auth/SecurityCheckProvider.java create mode 100644 src/main/java/com/commafeed/frontend/session/SessionHelperFactory.java create mode 100644 src/main/java/com/commafeed/frontend/session/SessionHelperFactoryProvider.java delete mode 100644 src/main/java/com/commafeed/frontend/session/SessionHelperProvider.java rename src/test/java/com/commafeed/frontend/auth/{SecurityCheckInjectableTest.java => SecurityCheckFactoryTest.java} (78%) diff --git a/CHANGELOG b/CHANGELOG index d714369d..d760cde9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +v 2.1.0 + - dropwizard upgrade to 0.8.0 v 2.0.3 - internet explorer ajax cache workaround - categories are now deletable again diff --git a/pom.xml b/pom.xml index 0b00a49f..82e7fb08 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.commafeed commafeed - 2.0.3 + 2.1.0-SNAPSHOT jar CommaFeed @@ -14,12 +14,25 @@ UTF-8 - 0.7.1 + 0.8.0-SNAPSHOT 3.0 3.5.0 1.5.0 + + + oss_snapshots + https://oss.sonatype.org/content/repositories/snapshots/ + + false + + + true + + + + scm:git:https://github.com/Athou/commafeed.git scm:git:https://github.com/Athou/commafeed.git @@ -220,7 +233,7 @@ ${dropwizard.version} pom - + com.wordnik swagger-jaxrs_2.10 @@ -232,7 +245,7 @@ - + com.mysema.querydsl querydsl-apt @@ -245,7 +258,12 @@ querydsl-jpa ${querydsl.version} - + + + commons-lang + commons-lang + 2.6 + commons-io commons-io @@ -266,7 +284,7 @@ commons-math3 3.3 - + redis.clients jedis @@ -277,7 +295,7 @@ javax.mail 1.5.2 - + com.rometools rome diff --git a/src/main/java/com/commafeed/CommaFeedApplication.java b/src/main/java/com/commafeed/CommaFeedApplication.java index 41018307..60651267 100644 --- a/src/main/java/com/commafeed/CommaFeedApplication.java +++ b/src/main/java/com/commafeed/CommaFeedApplication.java @@ -5,6 +5,7 @@ import io.dropwizard.assets.AssetsBundle; import io.dropwizard.db.DataSourceFactory; import io.dropwizard.hibernate.HibernateBundle; import io.dropwizard.migrations.MigrationsBundle; +import io.dropwizard.server.DefaultServerFactory; import io.dropwizard.servlets.CacheBustingFilter; import io.dropwizard.setup.Bootstrap; import io.dropwizard.setup.Environment; @@ -22,6 +23,7 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import org.eclipse.jetty.server.session.SessionHandler; +import org.glassfish.jersey.media.multipart.MultiPartFeature; import com.commafeed.backend.feed.FeedRefreshTaskGiver; import com.commafeed.backend.feed.FeedRefreshUpdater; @@ -41,8 +43,7 @@ import com.commafeed.backend.service.StartupService; import com.commafeed.backend.service.UserService; import com.commafeed.backend.task.OldStatusesCleanupTask; import com.commafeed.backend.task.OrphansCleanupTask; -import com.commafeed.frontend.auth.SecurityCheckProvider; -import com.commafeed.frontend.auth.SecurityCheckProvider.SecurityCheckUserServiceProvider; +import com.commafeed.frontend.auth.SecurityCheckFactoryProvider; import com.commafeed.frontend.resource.AdminREST; import com.commafeed.frontend.resource.CategoryREST; import com.commafeed.frontend.resource.EntryREST; @@ -54,10 +55,9 @@ import com.commafeed.frontend.servlet.AnalyticsServlet; import com.commafeed.frontend.servlet.CustomCssServlet; import com.commafeed.frontend.servlet.LogoutServlet; import com.commafeed.frontend.servlet.NextUnreadServlet; -import com.commafeed.frontend.session.SessionHelperProvider; +import com.commafeed.frontend.session.SessionHelperFactoryProvider; import com.google.inject.Guice; import com.google.inject.Injector; -import com.sun.jersey.api.core.ResourceConfig; import com.wordnik.swagger.config.ConfigFactory; import com.wordnik.swagger.config.ScannerFactory; import com.wordnik.swagger.config.SwaggerConfig; @@ -113,12 +113,12 @@ public class CommaFeedApplication extends Application { // Auth/session management environment.servlets().setSessionHandler(new SessionHandler(config.getSessionManagerFactory().build())); - environment.jersey().register(new SecurityCheckUserServiceProvider(injector.getInstance(UserService.class))); - environment.jersey().register(SecurityCheckProvider.class); - environment.jersey().register(SessionHelperProvider.class); + environment.jersey().register(new SecurityCheckFactoryProvider.Binder(injector.getInstance(UserService.class))); + environment.jersey().register(new SessionHelperFactoryProvider.Binder()); // REST resources environment.jersey().setUrlPattern("/rest/*"); + ((DefaultServerFactory) config.getServerFactory()).setJerseyRootPath("/rest/*"); environment.jersey().register(injector.getInstance(AdminREST.class)); environment.jersey().register(injector.getInstance(CategoryREST.class)); environment.jersey().register(injector.getInstance(EntryREST.class)); @@ -127,6 +127,8 @@ public class CommaFeedApplication extends Application { environment.jersey().register(injector.getInstance(ServerREST.class)); environment.jersey().register(injector.getInstance(UserREST.class)); + environment.jersey().register(MultiPartFeature.class); + // Servlets environment.servlets().addServlet("next", injector.getInstance(NextUnreadServlet.class)).addMapping("/next"); environment.servlets().addServlet("logout", injector.getInstance(LogoutServlet.class)).addMapping("/logout"); @@ -170,8 +172,6 @@ public class CommaFeedApplication extends Application { } }).addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/rest/*"); - // enable wadl - environment.jersey().disable(ResourceConfig.FEATURE_DISABLE_WADL); } public static void main(String[] args) throws Exception { diff --git a/src/main/java/com/commafeed/frontend/auth/SecurityCheckFactory.java b/src/main/java/com/commafeed/frontend/auth/SecurityCheckFactory.java new file mode 100644 index 00000000..6f4dd3c9 --- /dev/null +++ b/src/main/java/com/commafeed/frontend/auth/SecurityCheckFactory.java @@ -0,0 +1,96 @@ +package com.commafeed.frontend.auth; + +import javax.inject.Inject; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import lombok.RequiredArgsConstructor; + +import org.eclipse.jetty.util.B64Code; +import org.eclipse.jetty.util.StringUtil; +import org.glassfish.jersey.server.internal.inject.AbstractContainerRequestValueFactory; + +import com.commafeed.backend.model.User; +import com.commafeed.backend.model.UserRole.Role; +import com.commafeed.backend.service.UserService; +import com.commafeed.frontend.session.SessionHelper; +import com.google.common.base.Optional; + +@RequiredArgsConstructor +public class SecurityCheckFactory extends AbstractContainerRequestValueFactory { + + private static final String PREFIX = "Basic"; + + @Context + HttpServletRequest request; + + @Inject + UserService userService; + + private final Role role; + private final boolean apiKeyAllowed; + + @Override + public User provide() { + Optional user = apiKeyLogin(); + if (!user.isPresent()) { + user = basicAuthenticationLogin(); + } + if (!user.isPresent()) { + user = cookieSessionLogin(new SessionHelper(request)); + } + + if (user.isPresent()) { + if (user.get().hasRole(role)) { + return user.get(); + } else { + throw new WebApplicationException(Response.status(Response.Status.FORBIDDEN) + .entity("You don't have the required role to access this resource.").type(MediaType.TEXT_PLAIN_TYPE).build()); + } + } else { + throw new WebApplicationException(Response.status(Response.Status.UNAUTHORIZED) + .entity("Credentials are required to access this resource.").type(MediaType.TEXT_PLAIN_TYPE).build()); + } + } + + Optional cookieSessionLogin(SessionHelper sessionHelper) { + Optional loggedInUser = sessionHelper.getLoggedInUser(); + if (loggedInUser.isPresent()) { + userService.performPostLoginActivities(loggedInUser.get()); + } + return loggedInUser; + } + + private Optional basicAuthenticationLogin() { + String header = request.getHeader(HttpHeaders.AUTHORIZATION); + if (header != null) { + int space = header.indexOf(' '); + if (space > 0) { + String method = header.substring(0, space); + if (PREFIX.equalsIgnoreCase(method)) { + String decoded = B64Code.decode(header.substring(space + 1), StringUtil.__ISO_8859_1); + int i = decoded.indexOf(':'); + if (i > 0) { + String username = decoded.substring(0, i); + String password = decoded.substring(i + 1); + return userService.login(username, password); + } + } + } + } + return Optional.absent(); + } + + private Optional apiKeyLogin() { + String apiKey = request.getParameter("apiKey"); + if (apiKey != null && apiKeyAllowed) { + return userService.login(apiKey); + } + return Optional.absent(); + } + +} diff --git a/src/main/java/com/commafeed/frontend/auth/SecurityCheckFactoryProvider.java b/src/main/java/com/commafeed/frontend/auth/SecurityCheckFactoryProvider.java new file mode 100644 index 00000000..8d389c00 --- /dev/null +++ b/src/main/java/com/commafeed/frontend/auth/SecurityCheckFactoryProvider.java @@ -0,0 +1,64 @@ +package com.commafeed.frontend.auth; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import lombok.RequiredArgsConstructor; + +import org.glassfish.hk2.api.Factory; +import org.glassfish.hk2.api.InjectionResolver; +import org.glassfish.hk2.api.ServiceLocator; +import org.glassfish.hk2.api.TypeLiteral; +import org.glassfish.hk2.utilities.binding.AbstractBinder; +import org.glassfish.jersey.server.internal.inject.AbstractValueFactoryProvider; +import org.glassfish.jersey.server.internal.inject.MultivaluedParameterExtractorProvider; +import org.glassfish.jersey.server.internal.inject.ParamInjectionResolver; +import org.glassfish.jersey.server.model.Parameter; +import org.glassfish.jersey.server.spi.internal.ValueFactoryProvider; + +import com.commafeed.backend.model.User; +import com.commafeed.backend.service.UserService; + +@Singleton +public class SecurityCheckFactoryProvider extends AbstractValueFactoryProvider { + + @Inject + public SecurityCheckFactoryProvider(final MultivaluedParameterExtractorProvider extractorProvider, final ServiceLocator injector) { + super(extractorProvider, injector, Parameter.Source.UNKNOWN); + } + + @Override + protected Factory createValueFactory(final Parameter parameter) { + final Class classType = parameter.getRawType(); + + SecurityCheck securityCheck = parameter.getAnnotation(SecurityCheck.class); + if (securityCheck == null) + return null; + + if (classType.isAssignableFrom(User.class)) { + return new SecurityCheckFactory(securityCheck.value(), securityCheck.apiKeyAllowed()); + } else { + return null; + } + } + + public static class SecurityCheckInjectionResolver extends ParamInjectionResolver { + public SecurityCheckInjectionResolver() { + super(SecurityCheckFactoryProvider.class); + } + } + + @RequiredArgsConstructor + public static class Binder extends AbstractBinder { + + private final UserService userService; + + @Override + protected void configure() { + bind(SecurityCheckFactoryProvider.class).to(ValueFactoryProvider.class).in(Singleton.class); + bind(SecurityCheckInjectionResolver.class).to(new TypeLiteral>() { + }).in(Singleton.class); + bind(userService).to(UserService.class); + } + } +} diff --git a/src/main/java/com/commafeed/frontend/auth/SecurityCheckProvider.java b/src/main/java/com/commafeed/frontend/auth/SecurityCheckProvider.java deleted file mode 100644 index 030187e7..00000000 --- a/src/main/java/com/commafeed/frontend/auth/SecurityCheckProvider.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.commafeed.frontend.auth; - -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import lombok.RequiredArgsConstructor; - -import org.eclipse.jetty.util.B64Code; -import org.eclipse.jetty.util.StringUtil; - -import com.commafeed.backend.model.User; -import com.commafeed.backend.model.UserRole.Role; -import com.commafeed.backend.service.UserService; -import com.commafeed.frontend.session.SessionHelper; -import com.google.common.base.Optional; -import com.sun.jersey.api.core.HttpContext; -import com.sun.jersey.api.model.Parameter; -import com.sun.jersey.core.spi.component.ComponentContext; -import com.sun.jersey.core.spi.component.ComponentScope; -import com.sun.jersey.server.impl.inject.AbstractHttpContextInjectable; -import com.sun.jersey.spi.inject.Injectable; -import com.sun.jersey.spi.inject.InjectableProvider; -import com.sun.jersey.spi.inject.SingletonTypeInjectableProvider; - -public class SecurityCheckProvider implements InjectableProvider { - - public static class SecurityCheckUserServiceProvider extends SingletonTypeInjectableProvider { - - public SecurityCheckUserServiceProvider(UserService userService) { - super(UserService.class, userService); - } - } - - @RequiredArgsConstructor - static class SecurityCheckInjectable extends AbstractHttpContextInjectable { - private static final String PREFIX = "Basic"; - - private final SessionHelper sessionHelper; - private final UserService userService; - private final Role role; - private final boolean apiKeyAllowed; - - @Override - public User getValue(HttpContext c) { - Optional user = apiKeyLogin(c); - if (!user.isPresent()) { - user = basicAuthenticationLogin(c); - } - if (!user.isPresent()) { - user = cookieSessionLogin(); - } - - if (user.isPresent()) { - if (user.get().hasRole(role)) { - return user.get(); - } else { - throw new WebApplicationException(Response.status(Response.Status.FORBIDDEN) - .entity("You don't have the required role to access this resource.").type(MediaType.TEXT_PLAIN_TYPE).build()); - } - } else { - throw new WebApplicationException(Response.status(Response.Status.UNAUTHORIZED) - .entity("Credentials are required to access this resource.").type(MediaType.TEXT_PLAIN_TYPE).build()); - } - } - - Optional cookieSessionLogin() { - Optional loggedInUser = sessionHelper.getLoggedInUser(); - if (loggedInUser.isPresent()) { - userService.performPostLoginActivities(loggedInUser.get()); - } - return loggedInUser; - } - - private Optional basicAuthenticationLogin(HttpContext c) { - String header = c.getRequest().getHeaderValue(HttpHeaders.AUTHORIZATION); - if (header != null) { - int space = header.indexOf(' '); - if (space > 0) { - String method = header.substring(0, space); - if (PREFIX.equalsIgnoreCase(method)) { - String decoded = B64Code.decode(header.substring(space + 1), StringUtil.__ISO_8859_1); - int i = decoded.indexOf(':'); - if (i > 0) { - String username = decoded.substring(0, i); - String password = decoded.substring(i + 1); - return userService.login(username, password); - } - } - } - } - return Optional.absent(); - } - - private Optional apiKeyLogin(HttpContext c) { - String apiKey = c.getUriInfo().getQueryParameters().getFirst("apiKey"); - if (apiKey != null && apiKeyAllowed) { - return userService.login(apiKey); - } - return Optional.absent(); - } - } - - private SessionHelper sessionHelper; - private UserService userService; - - public SecurityCheckProvider(@Context HttpServletRequest req, @Context UserService userService) { - this.sessionHelper = new SessionHelper(req); - this.userService = userService; - } - - @Override - public ComponentScope getScope() { - return ComponentScope.PerRequest; - } - - @Override - public Injectable getInjectable(ComponentContext ic, SecurityCheck sc, Parameter c) { - return new SecurityCheckInjectable(sessionHelper, userService, sc.value(), sc.apiKeyAllowed()); - } -} diff --git a/src/main/java/com/commafeed/frontend/resource/FeedREST.java b/src/main/java/com/commafeed/frontend/resource/FeedREST.java index 7dc5cbba..cd1ef48a 100644 --- a/src/main/java/com/commafeed/frontend/resource/FeedREST.java +++ b/src/main/java/com/commafeed/frontend/resource/FeedREST.java @@ -35,6 +35,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.StringUtils; +import org.glassfish.jersey.media.multipart.FormDataParam; import com.commafeed.CommaFeedApplication; import com.commafeed.CommaFeedConfiguration; @@ -78,7 +79,6 @@ import com.rometools.rome.feed.synd.SyndFeed; import com.rometools.rome.feed.synd.SyndFeedImpl; import com.rometools.rome.io.SyndFeedOutput; import com.rometools.rome.io.WireFeedOutput; -import com.sun.jersey.multipart.FormDataParam; import com.wordnik.swagger.annotations.Api; import com.wordnik.swagger.annotations.ApiOperation; import com.wordnik.swagger.annotations.ApiParam; diff --git a/src/main/java/com/commafeed/frontend/session/SessionHelperFactory.java b/src/main/java/com/commafeed/frontend/session/SessionHelperFactory.java new file mode 100644 index 00000000..a6ecad6b --- /dev/null +++ b/src/main/java/com/commafeed/frontend/session/SessionHelperFactory.java @@ -0,0 +1,17 @@ +package com.commafeed.frontend.session; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.core.Context; + +import org.glassfish.jersey.server.internal.inject.AbstractContainerRequestValueFactory; + +public class SessionHelperFactory extends AbstractContainerRequestValueFactory { + + @Context + HttpServletRequest request; + + @Override + public SessionHelper provide() { + return new SessionHelper(request); + } +} \ No newline at end of file diff --git a/src/main/java/com/commafeed/frontend/session/SessionHelperFactoryProvider.java b/src/main/java/com/commafeed/frontend/session/SessionHelperFactoryProvider.java new file mode 100644 index 00000000..7eb0cbe9 --- /dev/null +++ b/src/main/java/com/commafeed/frontend/session/SessionHelperFactoryProvider.java @@ -0,0 +1,56 @@ +package com.commafeed.frontend.session; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.ws.rs.core.Context; + +import org.glassfish.hk2.api.Factory; +import org.glassfish.hk2.api.InjectionResolver; +import org.glassfish.hk2.api.ServiceLocator; +import org.glassfish.hk2.api.TypeLiteral; +import org.glassfish.hk2.utilities.binding.AbstractBinder; +import org.glassfish.jersey.server.internal.inject.AbstractValueFactoryProvider; +import org.glassfish.jersey.server.internal.inject.MultivaluedParameterExtractorProvider; +import org.glassfish.jersey.server.internal.inject.ParamInjectionResolver; +import org.glassfish.jersey.server.model.Parameter; +import org.glassfish.jersey.server.spi.internal.ValueFactoryProvider; + +@Singleton +public class SessionHelperFactoryProvider extends AbstractValueFactoryProvider { + + @Inject + public SessionHelperFactoryProvider(final MultivaluedParameterExtractorProvider extractorProvider, final ServiceLocator injector) { + super(extractorProvider, injector, Parameter.Source.CONTEXT); + } + + @Override + protected Factory createValueFactory(final Parameter parameter) { + final Class classType = parameter.getRawType(); + + Context context = parameter.getAnnotation(Context.class); + if (context == null) + return null; + + if (classType.isAssignableFrom(SessionHelper.class)) { + return new SessionHelperFactory(); + } else { + return null; + } + } + + public static class SessionHelperInjectionResolver extends ParamInjectionResolver { + public SessionHelperInjectionResolver() { + super(SessionHelperFactoryProvider.class); + } + } + + public static class Binder extends AbstractBinder { + + @Override + protected void configure() { + bind(SessionHelperFactoryProvider.class).to(ValueFactoryProvider.class).in(Singleton.class); + bind(SessionHelperInjectionResolver.class).to(new TypeLiteral>() { + }).in(Singleton.class); + } + } +} diff --git a/src/main/java/com/commafeed/frontend/session/SessionHelperProvider.java b/src/main/java/com/commafeed/frontend/session/SessionHelperProvider.java deleted file mode 100644 index aba77faf..00000000 --- a/src/main/java/com/commafeed/frontend/session/SessionHelperProvider.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.commafeed.frontend.session; - -import java.lang.reflect.Type; - -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.core.Context; -import javax.ws.rs.ext.Provider; - -import com.sun.jersey.core.spi.component.ComponentContext; -import com.sun.jersey.core.spi.component.ComponentScope; -import com.sun.jersey.spi.inject.Injectable; -import com.sun.jersey.spi.inject.InjectableProvider; - -@Provider -public class SessionHelperProvider implements InjectableProvider { - - private final ThreadLocal request; - - public SessionHelperProvider(@Context ThreadLocal request) { - this.request = request; - } - - @Override - public ComponentScope getScope() { - return ComponentScope.PerRequest; - } - - @Override - public Injectable getInjectable(ComponentContext ic, final Context session, Type type) { - if (type.equals(SessionHelper.class)) { - return new Injectable() { - @Override - public SessionHelper getValue() { - final HttpServletRequest req = request.get(); - if (req != null) { - return new SessionHelper(req); - } - return null; - } - }; - } - return null; - } -} \ No newline at end of file diff --git a/src/test/java/com/commafeed/frontend/auth/SecurityCheckInjectableTest.java b/src/test/java/com/commafeed/frontend/auth/SecurityCheckFactoryTest.java similarity index 78% rename from src/test/java/com/commafeed/frontend/auth/SecurityCheckInjectableTest.java rename to src/test/java/com/commafeed/frontend/auth/SecurityCheckFactoryTest.java index 429b055d..5c5f5861 100644 --- a/src/test/java/com/commafeed/frontend/auth/SecurityCheckInjectableTest.java +++ b/src/test/java/com/commafeed/frontend/auth/SecurityCheckFactoryTest.java @@ -9,11 +9,10 @@ import org.junit.Test; import com.commafeed.backend.model.User; import com.commafeed.backend.service.UserService; import com.commafeed.backend.service.internal.PostLoginActivities; -import com.commafeed.frontend.auth.SecurityCheckProvider.SecurityCheckInjectable; import com.commafeed.frontend.session.SessionHelper; import com.google.common.base.Optional; -public class SecurityCheckInjectableTest { +public class SecurityCheckFactoryTest { @Test public void cookie_login_should_perform_post_login_activities_if_user_is_logged_in() { @@ -26,8 +25,9 @@ public class SecurityCheckInjectableTest { UserService service = new UserService(null, null, null, null, null, postLoginActivities); - SecurityCheckInjectable injectable = new SecurityCheckInjectable(sessionHelper, service, null, false); - injectable.cookieSessionLogin(); + SecurityCheckFactory factory = new SecurityCheckFactory(null, false); + factory.userService = service; + factory.cookieSessionLogin(sessionHelper); verify(postLoginActivities).executeFor(userInSession); } From 24bd1121af7efccbd8e84bf4e4ef8aa3ab228331 Mon Sep 17 00:00:00 2001 From: Athou Date: Tue, 28 Oct 2014 16:36:09 +0100 Subject: [PATCH 002/241] commons-lang upgrade to v3 --- pom.xml | 9 ++++----- .../com/commafeed/CommaFeedConfiguration.java | 2 +- .../java/com/commafeed/CommaFeedModule.java | 2 +- .../java/com/commafeed/backend/HttpGetter.java | 4 ++-- .../backend/cache/RedisPoolFactory.java | 2 +- .../commafeed/backend/dao/FeedCategoryDAO.java | 4 ++-- .../com/commafeed/backend/dao/FeedDAO.java | 2 +- .../backend/dao/FeedEntryStatusDAO.java | 4 ++-- .../favicon/AbstractFaviconFetcher.java | 2 +- .../backend/favicon/DefaultFaviconFetcher.java | 2 +- .../commafeed/backend/feed/FeedFetcher.java | 2 +- .../com/commafeed/backend/feed/FeedParser.java | 5 ++--- .../com/commafeed/backend/feed/FeedQueues.java | 2 +- .../backend/feed/FeedRefreshUpdater.java | 4 ++-- .../backend/feed/FeedRefreshWorker.java | 4 ++-- .../com/commafeed/backend/feed/FeedUtils.java | 7 ++++--- .../java/com/commafeed/backend/model/User.java | 4 ++-- .../commafeed/backend/opml/OPMLImporter.java | 2 +- .../service/FeedEntryContentService.java | 2 +- .../service/FeedSubscriptionService.java | 2 +- .../service/PasswordEncryptionService.java | 2 +- .../backend/service/PubSubService.java | 2 +- .../commafeed/backend/service/UserService.java | 6 +++--- .../service/internal/PostLoginActivities.java | 6 +++--- .../commafeed/frontend/resource/AdminREST.java | 2 +- .../frontend/resource/CategoryREST.java | 11 ++++++----- .../commafeed/frontend/resource/FeedREST.java | 7 ++++--- .../resource/PubSubHubbubCallbackREST.java | 4 ++-- .../frontend/resource/ServerREST.java | 2 +- .../commafeed/frontend/resource/UserREST.java | 6 +++--- .../frontend/servlet/AnalyticsServlet.java | 2 +- .../frontend/servlet/NextUnreadServlet.java | 2 +- .../ics/crawler4j/url/URLCanonicalizer.java | 18 ++++++++---------- .../backend/FixedSizeSortedSetTest.java | 2 +- 34 files changed, 69 insertions(+), 70 deletions(-) diff --git a/pom.xml b/pom.xml index 82e7fb08..43157dd6 100644 --- a/pom.xml +++ b/pom.xml @@ -243,6 +243,10 @@ javax.ws.rs jsr311-api + + commons-lang + commons-lang + @@ -259,11 +263,6 @@ ${querydsl.version} - - commons-lang - commons-lang - 2.6 - commons-io commons-io diff --git a/src/main/java/com/commafeed/CommaFeedConfiguration.java b/src/main/java/com/commafeed/CommaFeedConfiguration.java index 5e8fc163..7e527308 100644 --- a/src/main/java/com/commafeed/CommaFeedConfiguration.java +++ b/src/main/java/com/commafeed/CommaFeedConfiguration.java @@ -12,7 +12,7 @@ import javax.validation.constraints.NotNull; import lombok.Getter; -import org.apache.commons.lang.time.DateUtils; +import org.apache.commons.lang3.time.DateUtils; import org.hibernate.validator.constraints.NotBlank; import com.commafeed.backend.cache.RedisPoolFactory; diff --git a/src/main/java/com/commafeed/CommaFeedModule.java b/src/main/java/com/commafeed/CommaFeedModule.java index a18b0be3..62d3911d 100644 --- a/src/main/java/com/commafeed/CommaFeedModule.java +++ b/src/main/java/com/commafeed/CommaFeedModule.java @@ -11,8 +11,8 @@ import com.commafeed.CommaFeedConfiguration.CacheType; import com.commafeed.backend.cache.CacheService; import com.commafeed.backend.cache.NoopCacheService; import com.commafeed.backend.cache.RedisCacheService; -import com.commafeed.backend.favicon.DefaultFaviconFetcher; import com.commafeed.backend.favicon.AbstractFaviconFetcher; +import com.commafeed.backend.favicon.DefaultFaviconFetcher; import com.commafeed.backend.favicon.YoutubeFaviconFetcher; import com.google.inject.AbstractModule; import com.google.inject.Provides; diff --git a/src/main/java/com/commafeed/backend/HttpGetter.java b/src/main/java/com/commafeed/backend/HttpGetter.java index bca16f9f..78921ae8 100644 --- a/src/main/java/com/commafeed/backend/HttpGetter.java +++ b/src/main/java/com/commafeed/backend/HttpGetter.java @@ -17,7 +17,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.http.Consts; import org.apache.http.Header; import org.apache.http.HttpEntity; @@ -53,7 +53,7 @@ public class HttpGetter { private static final String ACCEPT_LANGUAGE = "en"; private static final String PRAGMA_NO_CACHE = "No-cache"; private static final String CACHE_CONTROL_NO_CACHE = "no-cache"; - + private static final HttpResponseInterceptor REMOVE_INCORRECT_CONTENT_ENCODING = new ContentEncodingInterceptor(); private static SSLContext SSL_CONTEXT = null; diff --git a/src/main/java/com/commafeed/backend/cache/RedisPoolFactory.java b/src/main/java/com/commafeed/backend/cache/RedisPoolFactory.java index 302a2793..9cdb6671 100644 --- a/src/main/java/com/commafeed/backend/cache/RedisPoolFactory.java +++ b/src/main/java/com/commafeed/backend/cache/RedisPoolFactory.java @@ -2,7 +2,7 @@ package com.commafeed.backend.cache; import lombok.Getter; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; diff --git a/src/main/java/com/commafeed/backend/dao/FeedCategoryDAO.java b/src/main/java/com/commafeed/backend/dao/FeedCategoryDAO.java index d8e94817..7b5aac2d 100644 --- a/src/main/java/com/commafeed/backend/dao/FeedCategoryDAO.java +++ b/src/main/java/com/commafeed/backend/dao/FeedCategoryDAO.java @@ -1,11 +1,11 @@ package com.commafeed.backend.dao; import java.util.List; +import java.util.Objects; import javax.inject.Inject; import javax.inject.Singleton; -import org.apache.commons.lang.ObjectUtils; import org.hibernate.SessionFactory; import com.commafeed.backend.model.FeedCategory; @@ -70,7 +70,7 @@ public class FeedCategoryDAO extends GenericDAO { } boolean isChild = false; while (child != null) { - if (ObjectUtils.equals(child.getId(), parent.getId())) { + if (Objects.equals(child.getId(), parent.getId())) { isChild = true; break; } diff --git a/src/main/java/com/commafeed/backend/dao/FeedDAO.java b/src/main/java/com/commafeed/backend/dao/FeedDAO.java index f8dad975..bd1ebeb8 100644 --- a/src/main/java/com/commafeed/backend/dao/FeedDAO.java +++ b/src/main/java/com/commafeed/backend/dao/FeedDAO.java @@ -7,7 +7,7 @@ import javax.inject.Inject; import javax.inject.Singleton; import org.apache.commons.codec.digest.DigestUtils; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.hibernate.SessionFactory; import com.commafeed.backend.model.Feed; diff --git a/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java b/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java index a349c8ed..2472912b 100644 --- a/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java +++ b/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java @@ -7,8 +7,8 @@ import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.builder.CompareToBuilder; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.CompareToBuilder; import org.hibernate.SessionFactory; import com.commafeed.CommaFeedConfiguration; diff --git a/src/main/java/com/commafeed/backend/favicon/AbstractFaviconFetcher.java b/src/main/java/com/commafeed/backend/favicon/AbstractFaviconFetcher.java index bd10f197..ff71535c 100644 --- a/src/main/java/com/commafeed/backend/favicon/AbstractFaviconFetcher.java +++ b/src/main/java/com/commafeed/backend/favicon/AbstractFaviconFetcher.java @@ -5,7 +5,7 @@ import java.util.List; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import com.commafeed.backend.model.Feed; diff --git a/src/main/java/com/commafeed/backend/favicon/DefaultFaviconFetcher.java b/src/main/java/com/commafeed/backend/favicon/DefaultFaviconFetcher.java index d6b90f8b..b4e3da67 100644 --- a/src/main/java/com/commafeed/backend/favicon/DefaultFaviconFetcher.java +++ b/src/main/java/com/commafeed/backend/favicon/DefaultFaviconFetcher.java @@ -6,7 +6,7 @@ import javax.inject.Singleton; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.select.Elements; diff --git a/src/main/java/com/commafeed/backend/feed/FeedFetcher.java b/src/main/java/com/commafeed/backend/feed/FeedFetcher.java index c492f7f3..0929b72f 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedFetcher.java +++ b/src/main/java/com/commafeed/backend/feed/FeedFetcher.java @@ -45,7 +45,7 @@ public class FeedFetcher { } catch (FeedException e) { if (extractFeedUrlFromHtml) { String extractedUrl = extractFeedUrl(StringUtils.newStringUtf8(result.getContent()), feedUrl); - if (org.apache.commons.lang.StringUtils.isNotBlank(extractedUrl)) { + if (org.apache.commons.lang3.StringUtils.isNotBlank(extractedUrl)) { feedUrl = extractedUrl; result = getter.getBinary(extractedUrl, lastModified, eTag, timeout); diff --git a/src/main/java/com/commafeed/backend/feed/FeedParser.java b/src/main/java/com/commafeed/backend/feed/FeedParser.java index baf87413..2e0f807f 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedParser.java +++ b/src/main/java/com/commafeed/backend/feed/FeedParser.java @@ -11,8 +11,7 @@ import javax.inject.Singleton; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.SystemUtils; +import org.apache.commons.lang3.StringUtils; import org.jdom2.Element; import org.jdom2.Namespace; import org.xml.sax.InputSource; @@ -173,7 +172,7 @@ public class FeedParser { if (item.getContents().isEmpty()) { content = item.getDescription() == null ? null : item.getDescription().getValue(); } else { - content = StringUtils.join(Collections2.transform(item.getContents(), CONTENT_TO_STRING), SystemUtils.LINE_SEPARATOR); + content = StringUtils.join(Collections2.transform(item.getContents(), CONTENT_TO_STRING), System.lineSeparator()); } return StringUtils.trimToNull(content); } diff --git a/src/main/java/com/commafeed/backend/feed/FeedQueues.java b/src/main/java/com/commafeed/backend/feed/FeedQueues.java index c3c90bc8..288e4f2f 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedQueues.java +++ b/src/main/java/com/commafeed/backend/feed/FeedQueues.java @@ -9,7 +9,7 @@ import javax.inject.Inject; import javax.inject.Singleton; import org.apache.commons.codec.digest.DigestUtils; -import org.apache.commons.lang.time.DateUtils; +import org.apache.commons.lang3.time.DateUtils; import com.codahale.metrics.Gauge; import com.codahale.metrics.Meter; diff --git a/src/main/java/com/commafeed/backend/feed/FeedRefreshUpdater.java b/src/main/java/com/commafeed/backend/feed/FeedRefreshUpdater.java index e526a0a1..b80de7c0 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedRefreshUpdater.java +++ b/src/main/java/com/commafeed/backend/feed/FeedRefreshUpdater.java @@ -16,8 +16,8 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.time.DateUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.DateUtils; import org.hibernate.SessionFactory; import com.codahale.metrics.Meter; diff --git a/src/main/java/com/commafeed/backend/feed/FeedRefreshWorker.java b/src/main/java/com/commafeed/backend/feed/FeedRefreshWorker.java index 4192d3f1..70c66cae 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedRefreshWorker.java +++ b/src/main/java/com/commafeed/backend/feed/FeedRefreshWorker.java @@ -11,8 +11,8 @@ import javax.inject.Singleton; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.digest.DigestUtils; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.time.DateUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.DateUtils; import com.codahale.metrics.MetricRegistry; import com.commafeed.CommaFeedConfiguration; diff --git a/src/main/java/com/commafeed/backend/feed/FeedUtils.java b/src/main/java/com/commafeed/backend/feed/FeedUtils.java index c722a2b9..a8a8d2f1 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedUtils.java +++ b/src/main/java/com/commafeed/backend/feed/FeedUtils.java @@ -13,9 +13,9 @@ import java.util.regex.Pattern; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.binary.Base64; -import org.apache.commons.lang.ArrayUtils; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.time.DateUtils; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.math3.stat.descriptive.SummaryStatistics; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; @@ -517,4 +517,5 @@ public class FeedUtils { } } } + } diff --git a/src/main/java/com/commafeed/backend/model/User.java b/src/main/java/com/commafeed/backend/model/User.java index ddd3fab5..3b8a5126 100644 --- a/src/main/java/com/commafeed/backend/model/User.java +++ b/src/main/java/com/commafeed/backend/model/User.java @@ -15,7 +15,7 @@ import javax.persistence.TemporalType; import lombok.Getter; import lombok.Setter; -import org.apache.commons.lang.time.DateUtils; +import org.apache.commons.lang3.time.DateUtils; import org.hibernate.annotations.Cascade; import com.commafeed.backend.model.UserRole.Role; @@ -78,7 +78,7 @@ public class User extends AbstractModel { } return false; } - + public boolean shouldRefreshFeedsAt(Date when) { return (lastFullRefresh == null || lastFullRefreshMoreThan30MinutesBefore(when)); } diff --git a/src/main/java/com/commafeed/backend/opml/OPMLImporter.java b/src/main/java/com/commafeed/backend/opml/OPMLImporter.java index 6ff2ddff..3084f5b1 100644 --- a/src/main/java/com/commafeed/backend/opml/OPMLImporter.java +++ b/src/main/java/com/commafeed/backend/opml/OPMLImporter.java @@ -10,7 +10,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import com.commafeed.backend.cache.CacheService; import com.commafeed.backend.dao.FeedCategoryDAO; diff --git a/src/main/java/com/commafeed/backend/service/FeedEntryContentService.java b/src/main/java/com/commafeed/backend/service/FeedEntryContentService.java index 2a4e1011..d7bc7fdf 100644 --- a/src/main/java/com/commafeed/backend/service/FeedEntryContentService.java +++ b/src/main/java/com/commafeed/backend/service/FeedEntryContentService.java @@ -6,7 +6,7 @@ import javax.inject.Singleton; import lombok.RequiredArgsConstructor; import org.apache.commons.codec.digest.DigestUtils; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import com.commafeed.backend.dao.FeedEntryContentDAO; import com.commafeed.backend.feed.FeedUtils; diff --git a/src/main/java/com/commafeed/backend/service/FeedSubscriptionService.java b/src/main/java/com/commafeed/backend/service/FeedSubscriptionService.java index dbe6b542..950a7632 100644 --- a/src/main/java/com/commafeed/backend/service/FeedSubscriptionService.java +++ b/src/main/java/com/commafeed/backend/service/FeedSubscriptionService.java @@ -9,7 +9,7 @@ import javax.inject.Singleton; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import com.commafeed.CommaFeedConfiguration; import com.commafeed.backend.cache.CacheService; diff --git a/src/main/java/com/commafeed/backend/service/PasswordEncryptionService.java b/src/main/java/com/commafeed/backend/service/PasswordEncryptionService.java index 2a61b3c8..c387a271 100644 --- a/src/main/java/com/commafeed/backend/service/PasswordEncryptionService.java +++ b/src/main/java/com/commafeed/backend/service/PasswordEncryptionService.java @@ -15,7 +15,7 @@ import javax.inject.Singleton; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; // taken from http://www.javacodegeeks.com/2012/05/secure-password-storage-donts-dos-and.html @SuppressWarnings("serial") diff --git a/src/main/java/com/commafeed/backend/service/PubSubService.java b/src/main/java/com/commafeed/backend/service/PubSubService.java index 0a93240d..d05bd2b5 100644 --- a/src/main/java/com/commafeed/backend/service/PubSubService.java +++ b/src/main/java/com/commafeed/backend/service/PubSubService.java @@ -10,7 +10,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpHeaders; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; diff --git a/src/main/java/com/commafeed/backend/service/UserService.java b/src/main/java/com/commafeed/backend/service/UserService.java index 747de4fb..4a67461a 100644 --- a/src/main/java/com/commafeed/backend/service/UserService.java +++ b/src/main/java/com/commafeed/backend/service/UserService.java @@ -10,7 +10,7 @@ import javax.inject.Singleton; import lombok.RequiredArgsConstructor; import org.apache.commons.codec.digest.DigestUtils; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import com.commafeed.CommaFeedConfiguration; import com.commafeed.backend.dao.FeedCategoryDAO; @@ -33,7 +33,7 @@ public class UserService { private final PasswordEncryptionService encryptionService; private final CommaFeedConfiguration config; - + private final PostLoginActivities postLoginActivities; /** @@ -56,7 +56,7 @@ public class UserService { } } return Optional.absent(); - } + } /** * try to log in with given api key diff --git a/src/main/java/com/commafeed/backend/service/internal/PostLoginActivities.java b/src/main/java/com/commafeed/backend/service/internal/PostLoginActivities.java index 0466cb99..69f8042e 100644 --- a/src/main/java/com/commafeed/backend/service/internal/PostLoginActivities.java +++ b/src/main/java/com/commafeed/backend/service/internal/PostLoginActivities.java @@ -7,7 +7,7 @@ import javax.inject.Singleton; import lombok.RequiredArgsConstructor; -import org.apache.commons.lang.time.DateUtils; +import org.apache.commons.lang3.time.DateUtils; import com.commafeed.CommaFeedConfiguration; import com.commafeed.backend.dao.UserDAO; @@ -17,11 +17,11 @@ import com.commafeed.backend.service.FeedSubscriptionService; @RequiredArgsConstructor(onConstructor = @__({ @Inject })) @Singleton public class PostLoginActivities { - + private final UserDAO userDAO; private final FeedSubscriptionService feedSubscriptionService; private final CommaFeedConfiguration config; - + public void executeFor(User user) { Date lastLogin = user.getLastLogin(); Date now = new Date(); diff --git a/src/main/java/com/commafeed/frontend/resource/AdminREST.java b/src/main/java/com/commafeed/frontend/resource/AdminREST.java index 08e43ba5..96d30184 100644 --- a/src/main/java/com/commafeed/frontend/resource/AdminREST.java +++ b/src/main/java/com/commafeed/frontend/resource/AdminREST.java @@ -19,7 +19,7 @@ import javax.ws.rs.core.Response.Status; import lombok.RequiredArgsConstructor; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import com.codahale.metrics.MetricRegistry; import com.commafeed.CommaFeedApplication; diff --git a/src/main/java/com/commafeed/frontend/resource/CategoryREST.java b/src/main/java/com/commafeed/frontend/resource/CategoryREST.java index 73a5659a..fd69f7ad 100644 --- a/src/main/java/com/commafeed/frontend/resource/CategoryREST.java +++ b/src/main/java/com/commafeed/frontend/resource/CategoryREST.java @@ -9,6 +9,7 @@ import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import javax.inject.Inject; import javax.inject.Singleton; @@ -27,8 +28,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang.ObjectUtils; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; import com.commafeed.CommaFeedConfiguration; import com.commafeed.backend.cache.CacheService; @@ -359,7 +360,7 @@ public class CategoryREST { int existingIndex = -1; for (int i = 0; i < categories.size(); i++) { - if (ObjectUtils.equals(categories.get(i).getId(), category.getId())) { + if (Objects.equals(categories.get(i).getId(), category.getId())) { existingIndex = i; } } @@ -436,7 +437,7 @@ public class CategoryREST { category.setExpanded(true); for (FeedCategory c : categories) { - if ((id == null && c.getParent() == null) || (c.getParent() != null && ObjectUtils.equals(c.getParent().getId(), id))) { + if ((id == null && c.getParent() == null) || (c.getParent() != null && Objects.equals(c.getParent().getId(), id))) { Category child = buildCategory(c.getId(), categories, subscriptions, unreadCount); child.setId(String.valueOf(c.getId())); child.setName(c.getName()); @@ -457,7 +458,7 @@ public class CategoryREST { for (FeedSubscription subscription : subscriptions) { if ((id == null && subscription.getCategory() == null) - || (subscription.getCategory() != null && ObjectUtils.equals(subscription.getCategory().getId(), id))) { + || (subscription.getCategory() != null && Objects.equals(subscription.getCategory().getId(), id))) { UnreadCount uc = unreadCount.get(subscription.getId()); Subscription sub = Subscription.build(subscription, config.getApplicationSettings().getPublicUrl(), uc); category.getFeeds().add(sub); diff --git a/src/main/java/com/commafeed/frontend/resource/FeedREST.java b/src/main/java/com/commafeed/frontend/resource/FeedREST.java index cd1ef48a..711c6938 100644 --- a/src/main/java/com/commafeed/frontend/resource/FeedREST.java +++ b/src/main/java/com/commafeed/frontend/resource/FeedREST.java @@ -11,6 +11,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.List; +import java.util.Objects; import javax.inject.Inject; import javax.inject.Singleton; @@ -33,8 +34,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; -import org.apache.commons.lang.ObjectUtils; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; import org.glassfish.jersey.media.multipart.FormDataParam; import com.commafeed.CommaFeedApplication; @@ -439,7 +440,7 @@ public class FeedREST { int existingIndex = -1; for (int i = 0; i < subs.size(); i++) { - if (ObjectUtils.equals(subs.get(i).getId(), subscription.getId())) { + if (Objects.equals(subs.get(i).getId(), subscription.getId())) { existingIndex = i; } } diff --git a/src/main/java/com/commafeed/frontend/resource/PubSubHubbubCallbackREST.java b/src/main/java/com/commafeed/frontend/resource/PubSubHubbubCallbackREST.java index cf6750ca..716fde4b 100644 --- a/src/main/java/com/commafeed/frontend/resource/PubSubHubbubCallbackREST.java +++ b/src/main/java/com/commafeed/frontend/resource/PubSubHubbubCallbackREST.java @@ -23,8 +23,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; -import org.apache.commons.lang.ArrayUtils; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; import com.codahale.metrics.MetricRegistry; import com.commafeed.CommaFeedConfiguration; diff --git a/src/main/java/com/commafeed/frontend/resource/ServerREST.java b/src/main/java/com/commafeed/frontend/resource/ServerREST.java index e3623f5e..2407973d 100644 --- a/src/main/java/com/commafeed/frontend/resource/ServerREST.java +++ b/src/main/java/com/commafeed/frontend/resource/ServerREST.java @@ -15,7 +15,7 @@ import javax.ws.rs.core.Response.Status; import lombok.RequiredArgsConstructor; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import com.commafeed.CommaFeedConfiguration; import com.commafeed.backend.HttpGetter; diff --git a/src/main/java/com/commafeed/frontend/resource/UserREST.java b/src/main/java/com/commafeed/frontend/resource/UserREST.java index cbc450e0..54c9da3d 100644 --- a/src/main/java/com/commafeed/frontend/resource/UserREST.java +++ b/src/main/java/com/commafeed/frontend/resource/UserREST.java @@ -27,9 +27,9 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.digest.DigestUtils; -import org.apache.commons.lang.RandomStringUtils; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.time.DateUtils; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.DateUtils; import org.apache.http.client.utils.URIBuilder; import com.commafeed.CommaFeedApplication; diff --git a/src/main/java/com/commafeed/frontend/servlet/AnalyticsServlet.java b/src/main/java/com/commafeed/frontend/servlet/AnalyticsServlet.java index da51d07d..787576e6 100644 --- a/src/main/java/com/commafeed/frontend/servlet/AnalyticsServlet.java +++ b/src/main/java/com/commafeed/frontend/servlet/AnalyticsServlet.java @@ -9,7 +9,7 @@ import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import com.commafeed.CommaFeedConfiguration; diff --git a/src/main/java/com/commafeed/frontend/servlet/NextUnreadServlet.java b/src/main/java/com/commafeed/frontend/servlet/NextUnreadServlet.java index fe743b5f..a2c3f29f 100644 --- a/src/main/java/com/commafeed/frontend/servlet/NextUnreadServlet.java +++ b/src/main/java/com/commafeed/frontend/servlet/NextUnreadServlet.java @@ -12,7 +12,7 @@ import javax.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.hibernate.SessionFactory; import com.commafeed.CommaFeedConfiguration; diff --git a/src/main/java/edu/uci/ics/crawler4j/url/URLCanonicalizer.java b/src/main/java/edu/uci/ics/crawler4j/url/URLCanonicalizer.java index d78502d6..ba2ff26a 100644 --- a/src/main/java/edu/uci/ics/crawler4j/url/URLCanonicalizer.java +++ b/src/main/java/edu/uci/ics/crawler4j/url/URLCanonicalizer.java @@ -28,11 +28,11 @@ import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; /** - * See http://en.wikipedia.org/wiki/URL_normalization for a reference Note: some - * parts of the code are adapted from: http://stackoverflow.com/a/4057470/405418 + * See http://en.wikipedia.org/wiki/URL_normalization for a reference Note: some parts of the code are adapted from: + * http://stackoverflow.com/a/4057470/405418 * * @author Yasser Ganjisaffar */ @@ -46,7 +46,7 @@ public class URLCanonicalizer { try { URL canonicalURL = new URL(UrlResolver.resolveUrl(context == null ? "" : context, href)); - + String host = canonicalURL.getHost().toLowerCase(); if (StringUtils.isBlank(host)) { // This is an invalid Url. @@ -113,7 +113,7 @@ public class URLCanonicalizer { URL result = new URL(protocol, host, port, pathAndQueryString); return result.toExternalForm(); - + } catch (MalformedURLException ex) { return null; } catch (URISyntaxException ex) { @@ -122,8 +122,7 @@ public class URLCanonicalizer { } /** - * Takes a query string, separates the constituent name-value pairs, and - * stores them in a SortedMap ordered by lexicographical order. + * Takes a query string, separates the constituent name-value pairs, and stores them in a SortedMap ordered by lexicographical order. * * @return Null if there is no query string. */ @@ -149,7 +148,7 @@ public class URLCanonicalizer { params.put(tokens[0], ""); } break; - case 2: + case 2: params.put(tokens[0], tokens[1]); break; } @@ -188,8 +187,7 @@ public class URLCanonicalizer { } /** - * Percent-encode values according the RFC 3986. The built-in Java - * URLEncoder does not encode according to the RFC, so we make the extra + * Percent-encode values according the RFC 3986. The built-in Java URLEncoder does not encode according to the RFC, so we make the extra * replacements. * * @param string diff --git a/src/test/java/com/commafeed/backend/FixedSizeSortedSetTest.java b/src/test/java/com/commafeed/backend/FixedSizeSortedSetTest.java index 5a81d395..7af2aff8 100644 --- a/src/test/java/com/commafeed/backend/FixedSizeSortedSetTest.java +++ b/src/test/java/com/commafeed/backend/FixedSizeSortedSetTest.java @@ -2,7 +2,7 @@ package com.commafeed.backend; import java.util.Comparator; -import org.apache.commons.lang.ObjectUtils; +import org.apache.commons.lang3.ObjectUtils; import org.junit.Assert; import org.junit.Before; import org.junit.Test; From 64981308507e40992a26bf376dc9fc596cb617ee Mon Sep 17 00:00:00 2001 From: Athou Date: Tue, 28 Oct 2014 16:39:48 +0100 Subject: [PATCH 003/241] remove app.contextPath setting --- .openshift/config.mysql.yml | 3 --- CHANGELOG | 1 + config.dev.yml | 3 --- config.yml.example | 3 --- src/main/java/com/commafeed/CommaFeedApplication.java | 3 --- src/main/java/com/commafeed/CommaFeedConfiguration.java | 4 ---- 6 files changed, 1 insertion(+), 16 deletions(-) diff --git a/.openshift/config.mysql.yml b/.openshift/config.mysql.yml index 3cefeb46..04be9c88 100644 --- a/.openshift/config.mysql.yml +++ b/.openshift/config.mysql.yml @@ -1,9 +1,6 @@ # CommaFeed settings # ------------------ app: - # context path of the application - contextPath: / - # url used to access commafeed publicUrl: https://@OPENSHIFT_APP_DNS@/ diff --git a/CHANGELOG b/CHANGELOG index d760cde9..39e5971b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ v 2.1.0 - dropwizard upgrade to 0.8.0 + - remove our custom app.contextPath setting from config, use server.applicationContextPath from dropwizard instead v 2.0.3 - internet explorer ajax cache workaround - categories are now deletable again diff --git a/config.dev.yml b/config.dev.yml index e2ddf666..8a77db95 100644 --- a/config.dev.yml +++ b/config.dev.yml @@ -1,9 +1,6 @@ # CommaFeed settings # ------------------ app: - # context path of the application - contextPath: / - # url used to access commafeed publicUrl: http://localhost:8082/ diff --git a/config.yml.example b/config.yml.example index 9c7ae8c1..8c7bba8d 100644 --- a/config.yml.example +++ b/config.yml.example @@ -1,9 +1,6 @@ # CommaFeed settings # ------------------ app: - # context path of the application - contextPath: / - # url used to access commafeed publicUrl: http://localhost:8082/ diff --git a/src/main/java/com/commafeed/CommaFeedApplication.java b/src/main/java/com/commafeed/CommaFeedApplication.java index 60651267..0f0a2a3d 100644 --- a/src/main/java/com/commafeed/CommaFeedApplication.java +++ b/src/main/java/com/commafeed/CommaFeedApplication.java @@ -105,9 +105,6 @@ public class CommaFeedApplication extends Application { @Override public void run(CommaFeedConfiguration config, Environment environment) throws Exception { - // configure context path - environment.getApplicationContext().setContextPath(config.getApplicationSettings().getContextPath()); - // guice init Injector injector = Guice.createInjector(new CommaFeedModule(hibernateBundle.getSessionFactory(), config, environment.metrics())); diff --git a/src/main/java/com/commafeed/CommaFeedConfiguration.java b/src/main/java/com/commafeed/CommaFeedConfiguration.java index 7e527308..e67d76ab 100644 --- a/src/main/java/com/commafeed/CommaFeedConfiguration.java +++ b/src/main/java/com/commafeed/CommaFeedConfiguration.java @@ -62,10 +62,6 @@ public class CommaFeedConfiguration extends Configuration { @Getter public static class ApplicationSettings { - @NotNull - @NotBlank - private String contextPath; - @NotNull @NotBlank private String publicUrl; From 4b15ecbc1b9cc76a8b3c7063383457e5f17ce9da Mon Sep 17 00:00:00 2001 From: Athou Date: Wed, 29 Oct 2014 05:03:17 +0100 Subject: [PATCH 004/241] more comments --- src/main/java/com/commafeed/CommaFeedApplication.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/commafeed/CommaFeedApplication.java b/src/main/java/com/commafeed/CommaFeedApplication.java index 0f0a2a3d..13aff766 100644 --- a/src/main/java/com/commafeed/CommaFeedApplication.java +++ b/src/main/java/com/commafeed/CommaFeedApplication.java @@ -108,9 +108,12 @@ public class CommaFeedApplication extends Application { // guice init Injector injector = Guice.createInjector(new CommaFeedModule(hibernateBundle.getSessionFactory(), config, environment.metrics())); - // Auth/session management + // session management environment.servlets().setSessionHandler(new SessionHandler(config.getSessionManagerFactory().build())); + + // support for "@SecurityCheck User user" intection environment.jersey().register(new SecurityCheckFactoryProvider.Binder(injector.getInstance(UserService.class))); + // support for "@COntext SessionHelper sessionHelper" intection environment.jersey().register(new SessionHelperFactoryProvider.Binder()); // REST resources @@ -124,6 +127,7 @@ public class CommaFeedApplication extends Application { environment.jersey().register(injector.getInstance(ServerREST.class)); environment.jersey().register(injector.getInstance(UserREST.class)); + // @FormDataParam support environment.jersey().register(MultiPartFeature.class); // Servlets From 41f133afb16c177bd42e150e6b11cd92cc527445 Mon Sep 17 00:00:00 2001 From: Athou Date: Wed, 29 Oct 2014 05:21:40 +0100 Subject: [PATCH 005/241] liquibade upgrade fix --- src/main/resources/changelogs/db.changelog-1.2.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/changelogs/db.changelog-1.2.xml b/src/main/resources/changelogs/db.changelog-1.2.xml index 2139d0ce..8beb4a23 100644 --- a/src/main/resources/changelogs/db.changelog-1.2.xml +++ b/src/main/resources/changelogs/db.changelog-1.2.xml @@ -44,6 +44,7 @@ + 7:9bf9357b47d8666dc7916f9a318138ad From 7497a0151a91c03197260ab12feb8a1d086698c2 Mon Sep 17 00:00:00 2001 From: Athou Date: Wed, 29 Oct 2014 05:22:10 +0100 Subject: [PATCH 006/241] upgrade instructions --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 39e5971b..62228987 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,6 @@ v 2.1.0 - dropwizard upgrade to 0.8.0 - - remove our custom app.contextPath setting from config, use server.applicationContextPath from dropwizard instead + - you have to remove the "app.contextPath" setting from your yml file, you can optionnaly use server.applicationContextPath instead v 2.0.3 - internet explorer ajax cache workaround - categories are now deletable again From 5f28fd4114f61300fc76e71524defa3bd1a13193 Mon Sep 17 00:00:00 2001 From: Athou Date: Tue, 4 Nov 2014 11:23:58 +0100 Subject: [PATCH 007/241] initial support for entry filtering --- pom.xml | 6 ++ .../backend/feed/FeedEntryFilter.java | 89 +++++++++++++++++++ .../backend/feed/FeedRefreshUpdater.java | 2 +- .../backend/model/FeedSubscription.java | 3 + .../backend/service/FeedUpdateService.java | 20 ++++- .../resources/changelogs/db.changelog-2.1.xml | 11 +++ src/main/resources/migrations.xml | 1 + .../backend/feed/FeedEntryFilterTest.java | 64 +++++++++++++ 8 files changed, 193 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/commafeed/backend/feed/FeedEntryFilter.java create mode 100644 src/main/resources/changelogs/db.changelog-2.1.xml create mode 100644 src/test/java/com/commafeed/backend/feed/FeedEntryFilterTest.java diff --git a/pom.xml b/pom.xml index 43157dd6..29840754 100644 --- a/pom.xml +++ b/pom.xml @@ -191,6 +191,12 @@ 1.7.7 + + org.apache.commons + commons-jexl + 2.1.1 + + com.google.inject guice diff --git a/src/main/java/com/commafeed/backend/feed/FeedEntryFilter.java b/src/main/java/com/commafeed/backend/feed/FeedEntryFilter.java new file mode 100644 index 00000000..03d6fbd7 --- /dev/null +++ b/src/main/java/com/commafeed/backend/feed/FeedEntryFilter.java @@ -0,0 +1,89 @@ +package com.commafeed.backend.feed; + +import lombok.Data; +import lombok.RequiredArgsConstructor; + +import org.apache.commons.jexl2.Expression; +import org.apache.commons.jexl2.JexlContext; +import org.apache.commons.jexl2.JexlEngine; +import org.apache.commons.jexl2.JexlInfo; +import org.apache.commons.jexl2.MapContext; +import org.apache.commons.jexl2.introspection.JexlMethod; +import org.apache.commons.jexl2.introspection.JexlPropertyGet; +import org.apache.commons.jexl2.introspection.Uberspect; +import org.apache.commons.jexl2.introspection.UberspectImpl; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.logging.LogFactory; + +import com.commafeed.backend.model.FeedEntry; + +@RequiredArgsConstructor +public class FeedEntryFilter { + + private static final JexlEngine ENGINE = initEngine(); + + private static JexlEngine initEngine() { + // classloader that prevents object creation + ClassLoader cl = new ClassLoader() { + @Override + public Class loadClass(String name) throws ClassNotFoundException { + return null; + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + return null; + } + }; + + // uberspect that prevents access to .class and .getClass() + Uberspect uberspect = new UberspectImpl(LogFactory.getLog(JexlEngine.class)) { + @Override + public JexlPropertyGet getPropertyGet(Object obj, Object identifier, JexlInfo info) { + if ("class".equals(identifier)) { + return null; + } + return super.getPropertyGet(obj, identifier, info); + } + + @Override + public JexlMethod getMethod(Object obj, String method, Object[] args, JexlInfo info) { + if ("getClass".equals(method)) { + return null; + } + return super.getMethod(obj, method, args, info); + } + }; + + JexlEngine engine = new JexlEngine(uberspect, null, null, null); + engine.setClassLoader(cl); + return engine; + } + + private final String filter; + + public boolean matchesEntry(FeedEntry entry) { + if (StringUtils.isBlank(filter)) { + return true; + } + + Expression expression = ENGINE.createExpression(filter); + + JexlContext context = new MapContext(); + context.set("title", entry.getContent().getTitle().toLowerCase()); + context.set("author", entry.getContent().getAuthor().toLowerCase()); + context.set("content", entry.getContent().getContent().toLowerCase()); + context.set("url", entry.getUrl().toLowerCase()); + + return (boolean) expression.evaluate(context); + } + + @Data + private static class Model { + private String title; + private String author; + private String content; + private String url; + } + +} diff --git a/src/main/java/com/commafeed/backend/feed/FeedRefreshUpdater.java b/src/main/java/com/commafeed/backend/feed/FeedRefreshUpdater.java index b80de7c0..52beb7ca 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedRefreshUpdater.java +++ b/src/main/java/com/commafeed/backend/feed/FeedRefreshUpdater.java @@ -193,7 +193,7 @@ public class FeedRefreshUpdater implements Managed { boolean inserted = new UnitOfWork(sessionFactory) { @Override protected Boolean runInSession() throws Exception { - return feedUpdateService.addEntry(feed, entry); + return feedUpdateService.addEntry(feed, entry, subscriptions); } }.run(); if (inserted) { diff --git a/src/main/java/com/commafeed/backend/model/FeedSubscription.java b/src/main/java/com/commafeed/backend/model/FeedSubscription.java index 363665eb..5ef2e711 100644 --- a/src/main/java/com/commafeed/backend/model/FeedSubscription.java +++ b/src/main/java/com/commafeed/backend/model/FeedSubscription.java @@ -40,4 +40,7 @@ public class FeedSubscription extends AbstractModel { private Integer position; + @Column(length = 4096) + private String filter = "author.contains('a')"; + } diff --git a/src/main/java/com/commafeed/backend/service/FeedUpdateService.java b/src/main/java/com/commafeed/backend/service/FeedUpdateService.java index e0c68e17..0f51651e 100644 --- a/src/main/java/com/commafeed/backend/service/FeedUpdateService.java +++ b/src/main/java/com/commafeed/backend/service/FeedUpdateService.java @@ -1,6 +1,7 @@ package com.commafeed.backend.service; import java.util.Date; +import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; @@ -10,21 +11,26 @@ import lombok.RequiredArgsConstructor; import org.apache.commons.codec.digest.DigestUtils; import com.commafeed.backend.dao.FeedEntryDAO; +import com.commafeed.backend.dao.FeedEntryStatusDAO; +import com.commafeed.backend.feed.FeedEntryFilter; import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntryContent; +import com.commafeed.backend.model.FeedEntryStatus; +import com.commafeed.backend.model.FeedSubscription; @RequiredArgsConstructor(onConstructor = @__({ @Inject })) @Singleton public class FeedUpdateService { private final FeedEntryDAO feedEntryDAO; + private final FeedEntryStatusDAO feedEntryStatusDAO; private final FeedEntryContentService feedEntryContentService; /** * this is NOT thread-safe */ - public boolean addEntry(Feed feed, FeedEntry entry) { + public boolean addEntry(Feed feed, FeedEntry entry, List subscriptions) { Long existing = feedEntryDAO.findExisting(entry.getGuid(), feed); if (existing != null) { @@ -36,8 +42,18 @@ public class FeedUpdateService { entry.setContent(content); entry.setInserted(new Date()); entry.setFeed(feed); - feedEntryDAO.saveOrUpdate(entry); + + // if filter does not match the entry, mark it as read + for (FeedSubscription sub : subscriptions) { + FeedEntryFilter filter = new FeedEntryFilter(sub.getFilter()); + if (!filter.matchesEntry(entry)) { + FeedEntryStatus status = new FeedEntryStatus(sub.getUser(), sub, entry); + status.setRead(true); + feedEntryStatusDAO.saveOrUpdate(status); + } + } + return true; } } diff --git a/src/main/resources/changelogs/db.changelog-2.1.xml b/src/main/resources/changelogs/db.changelog-2.1.xml new file mode 100644 index 00000000..df158560 --- /dev/null +++ b/src/main/resources/changelogs/db.changelog-2.1.xml @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/src/main/resources/migrations.xml b/src/main/resources/migrations.xml index acc1dafa..8da3a73d 100644 --- a/src/main/resources/migrations.xml +++ b/src/main/resources/migrations.xml @@ -9,5 +9,6 @@ + \ No newline at end of file diff --git a/src/test/java/com/commafeed/backend/feed/FeedEntryFilterTest.java b/src/test/java/com/commafeed/backend/feed/FeedEntryFilterTest.java new file mode 100644 index 00000000..679dc63e --- /dev/null +++ b/src/test/java/com/commafeed/backend/feed/FeedEntryFilterTest.java @@ -0,0 +1,64 @@ +package com.commafeed.backend.feed; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.commafeed.backend.model.FeedEntry; +import com.commafeed.backend.model.FeedEntryContent; + +public class FeedEntryFilterTest { + + private FeedEntry entry; + private FeedEntryContent content; + + @Before + public void init() { + entry = new FeedEntry(); + entry.setUrl("https://github.com/Athou/commafeed"); + + content = new FeedEntryContent(); + content.setAuthor("Athou"); + content.setTitle("Merge pull request #662 from Athou/dw8"); + content.setContent("Merge pull request #662 from Athou/dw8"); + entry.setContent(content); + + } + + @Test + public void emptyFilterMatchesFilter() { + FeedEntryFilter filter = new FeedEntryFilter(null); + Assert.assertTrue(filter.matchesEntry(entry)); + } + + @Test + public void blankFilterMatchesFilter() { + FeedEntryFilter filter = new FeedEntryFilter(""); + Assert.assertTrue(filter.matchesEntry(entry)); + } + + @Test + public void simpleExpression() { + FeedEntryFilter filter = new FeedEntryFilter("author eq 'athou'"); + Assert.assertTrue(filter.matchesEntry(entry)); + } + + @Test + public void newIsDisabled() { + FeedEntryFilter filter = new FeedEntryFilter("null eq new ('java.lang.String', 'athou')"); + Assert.assertTrue(filter.matchesEntry(entry)); + } + + @Test + public void getClassMethodIsDisabled() { + FeedEntryFilter filter = new FeedEntryFilter("null eq ''.getClass()"); + Assert.assertTrue(filter.matchesEntry(entry)); + } + + @Test + public void dotClassIsDisabled() { + FeedEntryFilter filter = new FeedEntryFilter("null eq ''.class"); + Assert.assertTrue(filter.matchesEntry(entry)); + } + +} From a0c70d326fa2d98f977336ce9b8b1e0df5515069 Mon Sep 17 00:00:00 2001 From: Athou Date: Tue, 4 Nov 2014 15:09:45 +0100 Subject: [PATCH 008/241] class not used anymore --- .../com/commafeed/backend/feed/FeedEntryFilter.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/main/java/com/commafeed/backend/feed/FeedEntryFilter.java b/src/main/java/com/commafeed/backend/feed/FeedEntryFilter.java index 03d6fbd7..89bb8076 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedEntryFilter.java +++ b/src/main/java/com/commafeed/backend/feed/FeedEntryFilter.java @@ -1,6 +1,5 @@ package com.commafeed.backend.feed; -import lombok.Data; import lombok.RequiredArgsConstructor; import org.apache.commons.jexl2.Expression; @@ -77,13 +76,4 @@ public class FeedEntryFilter { return (boolean) expression.evaluate(context); } - - @Data - private static class Model { - private String title; - private String author; - private String content; - private String url; - } - } From 5ce2823d0be2fd80fc9d8c66e245d92bc9703919 Mon Sep 17 00:00:00 2001 From: Athou Date: Tue, 4 Nov 2014 15:22:43 +0100 Subject: [PATCH 009/241] strip html tags --- .../java/com/commafeed/backend/feed/FeedEntryFilter.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/commafeed/backend/feed/FeedEntryFilter.java b/src/main/java/com/commafeed/backend/feed/FeedEntryFilter.java index 89bb8076..bc43e0a4 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedEntryFilter.java +++ b/src/main/java/com/commafeed/backend/feed/FeedEntryFilter.java @@ -13,6 +13,7 @@ import org.apache.commons.jexl2.introspection.Uberspect; import org.apache.commons.jexl2.introspection.UberspectImpl; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.LogFactory; +import org.jsoup.Jsoup; import com.commafeed.backend.model.FeedEntry; @@ -69,9 +70,9 @@ public class FeedEntryFilter { Expression expression = ENGINE.createExpression(filter); JexlContext context = new MapContext(); - context.set("title", entry.getContent().getTitle().toLowerCase()); + context.set("title", Jsoup.parse(entry.getContent().getTitle()).text().toLowerCase()); context.set("author", entry.getContent().getAuthor().toLowerCase()); - context.set("content", entry.getContent().getContent().toLowerCase()); + context.set("content", Jsoup.parse(entry.getContent().getContent()).text().toLowerCase()); context.set("url", entry.getUrl().toLowerCase()); return (boolean) expression.evaluate(context); From 8f2ba5e186f7452dc11eadd1146f528a52c28da6 Mon Sep 17 00:00:00 2001 From: Athou Date: Tue, 4 Nov 2014 16:01:37 +0100 Subject: [PATCH 010/241] initial ui for entry filtering --- src/main/app/js/controllers.js | 10 +++++--- .../app/templates/feeds.feed_details.html | 8 ++++++ .../backend/feed/FeedEntryFilter.java | 1 + .../backend/model/FeedSubscription.java | 2 +- .../frontend/model/Subscription.java | 4 +++ .../request/FeedModificationRequest.java | 3 +++ .../commafeed/frontend/resource/FeedREST.java | 25 +++++++++++++++++++ 7 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/main/app/js/controllers.js b/src/main/app/js/controllers.js index 7ae8d904..9eb1747d 100644 --- a/src/main/app/js/controllers.js +++ b/src/main/app/js/controllers.js @@ -322,17 +322,21 @@ module.controller('FeedDetailsCtrl', ['$scope', '$state', '$stateParams', 'FeedS $scope.save = function() { var sub = $scope.sub; + $scope.error = null; FeedService.modify({ id : sub.id, name : sub.name, position : sub.position, - categoryId : sub.categoryId + categoryId : sub.categoryId, + filter : sub.filter }, function() { CategoryService.init(); $state.transitionTo('feeds.view', { _id : 'all', _type : 'category' }); + }, function(e) { + $scope.error = e.data; }); }; }]); @@ -489,7 +493,7 @@ module.controller('ToolbarCtrl', [ type : $stateParams._type, id : $stateParams._id, olderThan : olderThan, - keywords: $location.search().q, + keywords : $location.search().q, read : true }); }; @@ -882,7 +886,7 @@ module.controller('FeedListCtrl', [ service.mark({ id : $scope.selectedId, olderThan : olderThan || $scope.timestamp, - keywords: $location.search().q, + keywords : $location.search().q, read : true }, function() { CategoryService.refresh(function() { diff --git a/src/main/app/templates/feeds.feed_details.html b/src/main/app/templates/feeds.feed_details.html index 699b25f9..3a173bd6 100644 --- a/src/main/app/templates/feeds.feed_details.html +++ b/src/main/app/templates/feeds.feed_details.html @@ -3,6 +3,7 @@

{{ 'details.feed_details' | translate }}

+
{{ error }}
@@ -69,6 +70,13 @@
+
+ +
+ +
+
+
diff --git a/src/main/java/com/commafeed/backend/feed/FeedEntryFilter.java b/src/main/java/com/commafeed/backend/feed/FeedEntryFilter.java index bc43e0a4..79ab69c8 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedEntryFilter.java +++ b/src/main/java/com/commafeed/backend/feed/FeedEntryFilter.java @@ -56,6 +56,7 @@ public class FeedEntryFilter { }; JexlEngine engine = new JexlEngine(uberspect, null, null, null); + engine.setStrict(true); engine.setClassLoader(cl); return engine; } diff --git a/src/main/java/com/commafeed/backend/model/FeedSubscription.java b/src/main/java/com/commafeed/backend/model/FeedSubscription.java index 5ef2e711..92a08cec 100644 --- a/src/main/java/com/commafeed/backend/model/FeedSubscription.java +++ b/src/main/java/com/commafeed/backend/model/FeedSubscription.java @@ -41,6 +41,6 @@ public class FeedSubscription extends AbstractModel { private Integer position; @Column(length = 4096) - private String filter = "author.contains('a')"; + private String filter; } diff --git a/src/main/java/com/commafeed/frontend/model/Subscription.java b/src/main/java/com/commafeed/frontend/model/Subscription.java index a395b184..99e8d387 100644 --- a/src/main/java/com/commafeed/frontend/model/Subscription.java +++ b/src/main/java/com/commafeed/frontend/model/Subscription.java @@ -35,6 +35,7 @@ public class Subscription implements Serializable { sub.setUnread(unreadCount.getUnreadCount()); sub.setNewestItemTime(unreadCount.getNewestItemTime()); sub.setCategoryId(category == null ? null : String.valueOf(category.getId())); + sub.setFilter(subscription.getFilter()); return sub; } @@ -77,4 +78,7 @@ public class Subscription implements Serializable { @ApiModelProperty("date of the newest item") private Date newestItemTime; + @ApiModelProperty(value = "JEXL string evaluated on new entries to mark them as read if they do not match") + private String filter; + } \ No newline at end of file diff --git a/src/main/java/com/commafeed/frontend/model/request/FeedModificationRequest.java b/src/main/java/com/commafeed/frontend/model/request/FeedModificationRequest.java index c8b44876..595693e4 100644 --- a/src/main/java/com/commafeed/frontend/model/request/FeedModificationRequest.java +++ b/src/main/java/com/commafeed/frontend/model/request/FeedModificationRequest.java @@ -24,4 +24,7 @@ public class FeedModificationRequest implements Serializable { @ApiModelProperty(value = "new display position, null if not changed") private Integer position; + @ApiModelProperty(value = "JEXL string evaluated on new entries to mark them as read if they do not match") + private String filter; + } diff --git a/src/main/java/com/commafeed/frontend/resource/FeedREST.java b/src/main/java/com/commafeed/frontend/resource/FeedREST.java index cd29b06d..b16b349a 100644 --- a/src/main/java/com/commafeed/frontend/resource/FeedREST.java +++ b/src/main/java/com/commafeed/frontend/resource/FeedREST.java @@ -44,12 +44,15 @@ import com.commafeed.backend.cache.CacheService; import com.commafeed.backend.dao.FeedCategoryDAO; import com.commafeed.backend.dao.FeedEntryStatusDAO; import com.commafeed.backend.dao.FeedSubscriptionDAO; +import com.commafeed.backend.feed.FeedEntryFilter; import com.commafeed.backend.feed.FeedFetcher; import com.commafeed.backend.feed.FeedQueues; import com.commafeed.backend.feed.FeedUtils; import com.commafeed.backend.feed.FetchedFeed; import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.FeedCategory; +import com.commafeed.backend.model.FeedEntry; +import com.commafeed.backend.model.FeedEntryContent; import com.commafeed.backend.model.FeedEntryStatus; import com.commafeed.backend.model.FeedSubscription; import com.commafeed.backend.model.User; @@ -93,6 +96,20 @@ import com.wordnik.swagger.annotations.ApiParam; @Singleton public class FeedREST { + private static final FeedEntry TEST_ENTRY = initTestEntry(); + + private static FeedEntry initTestEntry() { + FeedEntry entry = new FeedEntry(); + entry.setUrl("https://github.com/Athou/commafeed"); + + FeedEntryContent content = new FeedEntryContent(); + content.setAuthor("Athou"); + content.setTitle("Merge pull request #662 from Athou/dw8"); + content.setContent("Merge pull request #662 from Athou/dw8"); + entry.setContent(content); + return entry; + } + private final FeedSubscriptionDAO feedSubscriptionDAO; private final FeedCategoryDAO feedCategoryDAO; private final FeedEntryStatusDAO feedEntryStatusDAO; @@ -418,7 +435,15 @@ public class FeedREST { Preconditions.checkNotNull(req); Preconditions.checkNotNull(req.getId()); + try { + new FeedEntryFilter(req.getFilter()).matchesEntry(TEST_ENTRY); + } catch (Exception e) { + Throwable root = Throwables.getRootCause(e); + return Response.status(Status.BAD_REQUEST).entity(root.getMessage()).build(); + } + FeedSubscription subscription = feedSubscriptionDAO.findById(user, req.getId()); + subscription.setFilter(req.getFilter()); if (StringUtils.isNotBlank(req.getName())) { subscription.setTitle(req.getName()); From c8fded3c565af37122ff949aed86af24a5b3b772 Mon Sep 17 00:00:00 2001 From: Athou Date: Tue, 4 Nov 2014 16:19:50 +0100 Subject: [PATCH 011/241] don't crash if we cannot evaluate the filter --- .../commafeed/backend/service/FeedUpdateService.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/commafeed/backend/service/FeedUpdateService.java b/src/main/java/com/commafeed/backend/service/FeedUpdateService.java index 0f51651e..033fc734 100644 --- a/src/main/java/com/commafeed/backend/service/FeedUpdateService.java +++ b/src/main/java/com/commafeed/backend/service/FeedUpdateService.java @@ -7,6 +7,7 @@ import javax.inject.Inject; import javax.inject.Singleton; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.digest.DigestUtils; @@ -19,6 +20,7 @@ import com.commafeed.backend.model.FeedEntryContent; import com.commafeed.backend.model.FeedEntryStatus; import com.commafeed.backend.model.FeedSubscription; +@Slf4j @RequiredArgsConstructor(onConstructor = @__({ @Inject })) @Singleton public class FeedUpdateService { @@ -47,7 +49,13 @@ public class FeedUpdateService { // if filter does not match the entry, mark it as read for (FeedSubscription sub : subscriptions) { FeedEntryFilter filter = new FeedEntryFilter(sub.getFilter()); - if (!filter.matchesEntry(entry)) { + boolean matches = true; + try { + matches = filter.matchesEntry(entry); + } catch (Exception e) { + log.error("could not evaluate filter {}", sub.getFilter(), e); + } + if (!matches) { FeedEntryStatus status = new FeedEntryStatus(sub.getUser(), sub, entry); status.setRead(true); feedEntryStatusDAO.saveOrUpdate(status); From eea0c24d2badb987f65987ab311d564b79a0fb69 Mon Sep 17 00:00:00 2001 From: Athou Date: Tue, 4 Nov 2014 16:25:34 +0100 Subject: [PATCH 012/241] engine is now strict and throws exceptions instead of returning nulls --- .../com/commafeed/backend/feed/FeedEntryFilterTest.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/commafeed/backend/feed/FeedEntryFilterTest.java b/src/test/java/com/commafeed/backend/feed/FeedEntryFilterTest.java index 679dc63e..256c1a4c 100644 --- a/src/test/java/com/commafeed/backend/feed/FeedEntryFilterTest.java +++ b/src/test/java/com/commafeed/backend/feed/FeedEntryFilterTest.java @@ -1,5 +1,6 @@ package com.commafeed.backend.feed; +import org.apache.commons.jexl2.JexlException; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -43,16 +44,16 @@ public class FeedEntryFilterTest { Assert.assertTrue(filter.matchesEntry(entry)); } - @Test + @Test(expected = JexlException.class) public void newIsDisabled() { FeedEntryFilter filter = new FeedEntryFilter("null eq new ('java.lang.String', 'athou')"); - Assert.assertTrue(filter.matchesEntry(entry)); + filter.matchesEntry(entry); } - @Test + @Test(expected = JexlException.class) public void getClassMethodIsDisabled() { FeedEntryFilter filter = new FeedEntryFilter("null eq ''.getClass()"); - Assert.assertTrue(filter.matchesEntry(entry)); + filter.matchesEntry(entry); } @Test From a94ef980bbe03b265b0249a83fdb5c9eee4e8c32 Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 7 Nov 2014 08:46:09 +0100 Subject: [PATCH 013/241] cannot loop forever --- .../backend/feed/FeedEntryFilter.java | 40 +++++++++++++++++-- .../backend/service/FeedUpdateService.java | 3 +- .../commafeed/frontend/resource/FeedREST.java | 3 +- .../backend/feed/FeedEntryFilterTest.java | 24 ++++++----- 4 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/commafeed/backend/feed/FeedEntryFilter.java b/src/main/java/com/commafeed/backend/feed/FeedEntryFilter.java index 79ab69c8..f9ca6a5e 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedEntryFilter.java +++ b/src/main/java/com/commafeed/backend/feed/FeedEntryFilter.java @@ -1,12 +1,20 @@ package com.commafeed.backend.feed; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + import lombok.RequiredArgsConstructor; -import org.apache.commons.jexl2.Expression; import org.apache.commons.jexl2.JexlContext; import org.apache.commons.jexl2.JexlEngine; +import org.apache.commons.jexl2.JexlException; import org.apache.commons.jexl2.JexlInfo; import org.apache.commons.jexl2.MapContext; +import org.apache.commons.jexl2.Script; import org.apache.commons.jexl2.introspection.JexlMethod; import org.apache.commons.jexl2.introspection.JexlPropertyGet; import org.apache.commons.jexl2.introspection.Uberspect; @@ -63,12 +71,17 @@ public class FeedEntryFilter { private final String filter; - public boolean matchesEntry(FeedEntry entry) { + public boolean matchesEntry(FeedEntry entry) throws FeedEntryFilterException { if (StringUtils.isBlank(filter)) { return true; } - Expression expression = ENGINE.createExpression(filter); + Script script = null; + try { + script = ENGINE.createScript(filter); + } catch (JexlException e) { + throw new FeedEntryFilterException("Exception while parsing expression " + filter, e); + } JexlContext context = new MapContext(); context.set("title", Jsoup.parse(entry.getContent().getTitle()).text().toLowerCase()); @@ -76,6 +89,25 @@ public class FeedEntryFilter { context.set("content", Jsoup.parse(entry.getContent().getContent()).text().toLowerCase()); context.set("url", entry.getUrl().toLowerCase()); - return (boolean) expression.evaluate(context); + Callable callable = script.callable(context); + Future future = Executors.newFixedThreadPool(1).submit(callable); + Object result = null; + try { + result = future.get(500, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + throw new FeedEntryFilterException("interrupted while evaluating expression " + filter, e); + } catch (ExecutionException e) { + throw new FeedEntryFilterException("Exception while evaluating expression " + filter, e); + } catch (TimeoutException e) { + throw new FeedEntryFilterException("Took too long evaluating expression " + filter, e); + } + return (boolean) result; + } + + @SuppressWarnings("serial") + public static class FeedEntryFilterException extends Exception { + public FeedEntryFilterException(String message, Throwable t) { + super(message, t); + } } } diff --git a/src/main/java/com/commafeed/backend/service/FeedUpdateService.java b/src/main/java/com/commafeed/backend/service/FeedUpdateService.java index 033fc734..437268af 100644 --- a/src/main/java/com/commafeed/backend/service/FeedUpdateService.java +++ b/src/main/java/com/commafeed/backend/service/FeedUpdateService.java @@ -14,6 +14,7 @@ import org.apache.commons.codec.digest.DigestUtils; import com.commafeed.backend.dao.FeedEntryDAO; import com.commafeed.backend.dao.FeedEntryStatusDAO; import com.commafeed.backend.feed.FeedEntryFilter; +import com.commafeed.backend.feed.FeedEntryFilter.FeedEntryFilterException; import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntryContent; @@ -52,7 +53,7 @@ public class FeedUpdateService { boolean matches = true; try { matches = filter.matchesEntry(entry); - } catch (Exception e) { + } catch (FeedEntryFilterException e) { log.error("could not evaluate filter {}", sub.getFilter(), e); } if (!matches) { diff --git a/src/main/java/com/commafeed/frontend/resource/FeedREST.java b/src/main/java/com/commafeed/frontend/resource/FeedREST.java index b16b349a..b9382d39 100644 --- a/src/main/java/com/commafeed/frontend/resource/FeedREST.java +++ b/src/main/java/com/commafeed/frontend/resource/FeedREST.java @@ -45,6 +45,7 @@ import com.commafeed.backend.dao.FeedCategoryDAO; import com.commafeed.backend.dao.FeedEntryStatusDAO; import com.commafeed.backend.dao.FeedSubscriptionDAO; import com.commafeed.backend.feed.FeedEntryFilter; +import com.commafeed.backend.feed.FeedEntryFilter.FeedEntryFilterException; import com.commafeed.backend.feed.FeedFetcher; import com.commafeed.backend.feed.FeedQueues; import com.commafeed.backend.feed.FeedUtils; @@ -437,7 +438,7 @@ public class FeedREST { try { new FeedEntryFilter(req.getFilter()).matchesEntry(TEST_ENTRY); - } catch (Exception e) { + } catch (FeedEntryFilterException e) { Throwable root = Throwables.getRootCause(e); return Response.status(Status.BAD_REQUEST).entity(root.getMessage()).build(); } diff --git a/src/test/java/com/commafeed/backend/feed/FeedEntryFilterTest.java b/src/test/java/com/commafeed/backend/feed/FeedEntryFilterTest.java index 256c1a4c..869710b6 100644 --- a/src/test/java/com/commafeed/backend/feed/FeedEntryFilterTest.java +++ b/src/test/java/com/commafeed/backend/feed/FeedEntryFilterTest.java @@ -1,10 +1,10 @@ package com.commafeed.backend.feed; -import org.apache.commons.jexl2.JexlException; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import com.commafeed.backend.feed.FeedEntryFilter.FeedEntryFilterException; import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntryContent; @@ -27,39 +27,45 @@ public class FeedEntryFilterTest { } @Test - public void emptyFilterMatchesFilter() { + public void emptyFilterMatchesFilter() throws FeedEntryFilterException { FeedEntryFilter filter = new FeedEntryFilter(null); Assert.assertTrue(filter.matchesEntry(entry)); } @Test - public void blankFilterMatchesFilter() { + public void blankFilterMatchesFilter() throws FeedEntryFilterException { FeedEntryFilter filter = new FeedEntryFilter(""); Assert.assertTrue(filter.matchesEntry(entry)); } @Test - public void simpleExpression() { + public void simpleExpression() throws FeedEntryFilterException { FeedEntryFilter filter = new FeedEntryFilter("author eq 'athou'"); Assert.assertTrue(filter.matchesEntry(entry)); } - @Test(expected = JexlException.class) - public void newIsDisabled() { + @Test(expected = FeedEntryFilterException.class) + public void newIsDisabled() throws FeedEntryFilterException { FeedEntryFilter filter = new FeedEntryFilter("null eq new ('java.lang.String', 'athou')"); filter.matchesEntry(entry); } - @Test(expected = JexlException.class) - public void getClassMethodIsDisabled() { + @Test(expected = FeedEntryFilterException.class) + public void getClassMethodIsDisabled() throws FeedEntryFilterException { FeedEntryFilter filter = new FeedEntryFilter("null eq ''.getClass()"); filter.matchesEntry(entry); } @Test - public void dotClassIsDisabled() { + public void dotClassIsDisabled() throws FeedEntryFilterException { FeedEntryFilter filter = new FeedEntryFilter("null eq ''.class"); Assert.assertTrue(filter.matchesEntry(entry)); } + @Test(expected = FeedEntryFilterException.class) + public void cannotLoopForever() throws FeedEntryFilterException { + FeedEntryFilter filter = new FeedEntryFilter("while(true) {}"); + filter.matchesEntry(entry); + } + } From 97c2cc3d150f3abaa69bdaa66633060379b378b4 Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 7 Nov 2014 09:00:53 +0100 Subject: [PATCH 014/241] unit tests for opml importer (#636) --- .../backend/opml/OPMLImporterTest.java | 52 +++++++++++++++++++ src/test/resources/opml/opml_noversion.xml | 11 ++++ src/test/resources/opml/opml_v1.0.xml | 11 ++++ src/test/resources/opml/opml_v1.1.xml | 11 ++++ src/test/resources/opml/opml_v2.0.xml | 11 ++++ 5 files changed, 96 insertions(+) create mode 100644 src/test/java/com/commafeed/backend/opml/OPMLImporterTest.java create mode 100644 src/test/resources/opml/opml_noversion.xml create mode 100644 src/test/resources/opml/opml_v1.0.xml create mode 100644 src/test/resources/opml/opml_v1.1.xml create mode 100644 src/test/resources/opml/opml_v2.0.xml diff --git a/src/test/java/com/commafeed/backend/opml/OPMLImporterTest.java b/src/test/java/com/commafeed/backend/opml/OPMLImporterTest.java new file mode 100644 index 00000000..99b63bd0 --- /dev/null +++ b/src/test/java/com/commafeed/backend/opml/OPMLImporterTest.java @@ -0,0 +1,52 @@ +package com.commafeed.backend.opml; + +import java.io.IOException; + +import org.apache.commons.io.IOUtils; +import org.junit.Test; +import org.mockito.Mockito; + +import com.commafeed.backend.cache.CacheService; +import com.commafeed.backend.dao.FeedCategoryDAO; +import com.commafeed.backend.model.FeedCategory; +import com.commafeed.backend.model.User; +import com.commafeed.backend.service.FeedSubscriptionService; + +public class OPMLImporterTest { + + @Test + public void testOpmlV10() throws IOException { + testOpmlVersion("/opml/opml_v1.0.xml"); + } + + @Test + public void testOpmlV11() throws IOException { + testOpmlVersion("/opml/opml_v1.1.xml"); + } + + @Test + public void testOpmlV20() throws IOException { + testOpmlVersion("/opml/opml_v2.0.xml"); + } + + @Test + public void testOpmlNoVersion() throws IOException { + testOpmlVersion("/opml/opml_noversion.xml"); + } + + private void testOpmlVersion(String fileName) throws IOException { + FeedCategoryDAO feedCategoryDAO = Mockito.mock(FeedCategoryDAO.class); + FeedSubscriptionService feedSubscriptionService = Mockito.mock(FeedSubscriptionService.class); + CacheService cacheService = Mockito.mock(CacheService.class); + User user = Mockito.mock(User.class); + + String xml = IOUtils.toString(getClass().getResourceAsStream(fileName)); + + OPMLImporter importer = new OPMLImporter(feedCategoryDAO, feedSubscriptionService, cacheService); + importer.importOpml(user, xml); + + Mockito.verify(feedSubscriptionService).subscribe(Mockito.eq(user), Mockito.anyString(), Mockito.anyString(), + Mockito.any(FeedCategory.class)); + } + +} diff --git a/src/test/resources/opml/opml_noversion.xml b/src/test/resources/opml/opml_noversion.xml new file mode 100644 index 00000000..4bcdbe0e --- /dev/null +++ b/src/test/resources/opml/opml_noversion.xml @@ -0,0 +1,11 @@ + + + + subscriptions + + + + + + + diff --git a/src/test/resources/opml/opml_v1.0.xml b/src/test/resources/opml/opml_v1.0.xml new file mode 100644 index 00000000..61ac899e --- /dev/null +++ b/src/test/resources/opml/opml_v1.0.xml @@ -0,0 +1,11 @@ + + + + subscriptions + + + + + + + diff --git a/src/test/resources/opml/opml_v1.1.xml b/src/test/resources/opml/opml_v1.1.xml new file mode 100644 index 00000000..4e2bedea --- /dev/null +++ b/src/test/resources/opml/opml_v1.1.xml @@ -0,0 +1,11 @@ + + + + subscriptions + + + + + + + diff --git a/src/test/resources/opml/opml_v2.0.xml b/src/test/resources/opml/opml_v2.0.xml new file mode 100644 index 00000000..29c70d83 --- /dev/null +++ b/src/test/resources/opml/opml_v2.0.xml @@ -0,0 +1,11 @@ + + + + subscriptions + + + + + + + From c0557856a3de0ecae541ca12decb1a0fe739941c Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 7 Nov 2014 09:38:55 +0100 Subject: [PATCH 015/241] configurable "from" address (fix #664) --- src/main/java/com/commafeed/CommaFeedConfiguration.java | 2 ++ src/main/java/com/commafeed/backend/service/MailService.java | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/commafeed/CommaFeedConfiguration.java b/src/main/java/com/commafeed/CommaFeedConfiguration.java index e67d76ab..8b41b4de 100644 --- a/src/main/java/com/commafeed/CommaFeedConfiguration.java +++ b/src/main/java/com/commafeed/CommaFeedConfiguration.java @@ -89,6 +89,8 @@ public class CommaFeedConfiguration extends Configuration { private String smtpPassword; + private String smtpFromAddress; + @NotNull private boolean heavyLoad; diff --git a/src/main/java/com/commafeed/backend/service/MailService.java b/src/main/java/com/commafeed/backend/service/MailService.java index 63725079..2e9d2186 100644 --- a/src/main/java/com/commafeed/backend/service/MailService.java +++ b/src/main/java/com/commafeed/backend/service/MailService.java @@ -17,6 +17,7 @@ import lombok.RequiredArgsConstructor; import com.commafeed.CommaFeedConfiguration; import com.commafeed.CommaFeedConfiguration.ApplicationSettings; import com.commafeed.backend.model.User; +import com.google.common.base.Optional; /** * Mailing service @@ -34,6 +35,7 @@ public class MailService { final String username = settings.getSmtpUserName(); final String password = settings.getSmtpPassword(); + final String fromAddress = Optional.fromNullable(settings.getSmtpFromAddress()).or(settings.getSmtpUserName()); String dest = user.getEmail(); @@ -51,7 +53,7 @@ public class MailService { }); Message message = new MimeMessage(session); - message.setFrom(new InternetAddress(username, "CommaFeed")); + message.setFrom(new InternetAddress(fromAddress, "CommaFeed")); message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(dest)); message.setSubject("CommaFeed - " + subject); message.setContent(content, "text/html; charset=utf-8"); From 928cf1220ef8b1a08bfb99f4ebf038fec053b121 Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 7 Nov 2014 09:41:01 +0100 Subject: [PATCH 016/241] config placeholder --- config.yml.example | 1 + 1 file changed, 1 insertion(+) diff --git a/config.yml.example b/config.yml.example index 8c7bba8d..77b717cd 100644 --- a/config.yml.example +++ b/config.yml.example @@ -22,6 +22,7 @@ app: smtpTls: false smtpUserName: smtpPassword: + smtpFromAddress: # wether this commafeed instance has a lot of feeds to refresh # leave this to false in almost all cases From 9b2cdbbb1839b49d5bb34e8d635372c5b362e010 Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 7 Nov 2014 10:26:58 +0100 Subject: [PATCH 017/241] fix readability icon position on safari (fix #651) --- src/main/app/sass/components/readabilicons.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/app/sass/components/readabilicons.scss b/src/main/app/sass/components/readabilicons.scss index 5e65e7f6..ed237ba8 100644 --- a/src/main/app/sass/components/readabilicons.scss +++ b/src/main/app/sass/components/readabilicons.scss @@ -13,8 +13,4 @@ content: "\e018"; font-family: "readabilicons"; -webkit-font-smoothing: antialiased; - font-size: 21px; - top: 5px; - position: relative; - line-height: 0px; } \ No newline at end of file From 1c999294290ffd451310d003541f71bc52c1bd2f Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 7 Nov 2014 10:53:49 +0100 Subject: [PATCH 018/241] exclude terms from search (fix #666) --- .../backend/dao/FeedEntryStatusDAO.java | 21 ++++++---- .../backend/feed/FeedEntryKeyword.java | 40 +++++++++++++++++++ .../com/commafeed/backend/feed/FeedUtils.java | 17 ++++---- .../backend/service/FeedEntryService.java | 7 ++-- .../frontend/resource/CategoryREST.java | 15 ++++--- .../commafeed/frontend/resource/FeedREST.java | 11 +++-- 6 files changed, 82 insertions(+), 29 deletions(-) create mode 100644 src/main/java/com/commafeed/backend/feed/FeedEntryKeyword.java diff --git a/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java b/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java index 2472912b..765b52fd 100644 --- a/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java +++ b/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java @@ -7,12 +7,13 @@ import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; -import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.CompareToBuilder; import org.hibernate.SessionFactory; import com.commafeed.CommaFeedConfiguration; import com.commafeed.backend.FixedSizeSortedSet; +import com.commafeed.backend.feed.FeedEntryKeyword; +import com.commafeed.backend.feed.FeedEntryKeyword.Mode; import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntryStatus; import com.commafeed.backend.model.FeedEntryTag; @@ -112,18 +113,21 @@ public class FeedEntryStatusDAO extends GenericDAO { return lazyLoadContent(includeContent, statuses); } - private HibernateQuery buildQuery(User user, FeedSubscription sub, boolean unreadOnly, String keywords, Date newerThan, int offset, - int limit, ReadingOrder order, Date last, String tag) { + private HibernateQuery buildQuery(User user, FeedSubscription sub, boolean unreadOnly, List keywords, Date newerThan, + int offset, int limit, ReadingOrder order, Date last, String tag) { HibernateQuery query = newQuery().from(entry).where(entry.feed.eq(sub.getFeed())); if (keywords != null) { query.join(entry.content, content); - for (String keyword : StringUtils.split(keywords)) { + for (FeedEntryKeyword keyword : keywords) { BooleanBuilder or = new BooleanBuilder(); - or.or(content.content.containsIgnoreCase(keyword)); - or.or(content.title.containsIgnoreCase(keyword)); + or.or(content.content.containsIgnoreCase(keyword.getKeyword())); + or.or(content.title.containsIgnoreCase(keyword.getKeyword())); + if (keyword.getMode() == Mode.EXCLUDE) { + or.not(); + } query.where(or); } } @@ -180,8 +184,9 @@ public class FeedEntryStatusDAO extends GenericDAO { return query; } - public List findBySubscriptions(User user, List subs, boolean unreadOnly, String keywords, - Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent, boolean onlyIds, String tag) { + public List findBySubscriptions(User user, List subs, boolean unreadOnly, + List keywords, Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent, + boolean onlyIds, String tag) { int capacity = offset + limit; Comparator comparator = order == ReadingOrder.desc ? STATUS_COMPARATOR_DESC : STATUS_COMPARATOR_ASC; FixedSizeSortedSet set = new FixedSizeSortedSet(capacity, comparator); diff --git a/src/main/java/com/commafeed/backend/feed/FeedEntryKeyword.java b/src/main/java/com/commafeed/backend/feed/FeedEntryKeyword.java new file mode 100644 index 00000000..46e7fb6f --- /dev/null +++ b/src/main/java/com/commafeed/backend/feed/FeedEntryKeyword.java @@ -0,0 +1,40 @@ +package com.commafeed.backend.feed; + +import java.util.List; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import org.apache.commons.lang3.StringUtils; + +import com.google.common.collect.Lists; + +/** + * A keyword used in a search query + */ +@Getter +@RequiredArgsConstructor +public class FeedEntryKeyword { + + public static enum Mode { + INCLUDE, EXCLUDE; + } + + private final String keyword; + private final Mode mode; + + public static List fromQueryString(String keywords) { + List list = Lists.newArrayList(); + if (keywords != null) { + for (String keyword : StringUtils.split(keywords)) { + boolean not = false; + if (keyword.startsWith("-") || keyword.startsWith("!")) { + not = true; + keyword = keyword.substring(1); + } + list.add(new FeedEntryKeyword(keyword, not ? Mode.EXCLUDE : Mode.INCLUDE)); + } + } + return list; + } +} diff --git a/src/main/java/com/commafeed/backend/feed/FeedUtils.java b/src/main/java/com/commafeed/backend/feed/FeedUtils.java index a8a8d2f1..fca78591 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedUtils.java +++ b/src/main/java/com/commafeed/backend/feed/FeedUtils.java @@ -29,6 +29,7 @@ import org.mozilla.universalchardet.UniversalDetector; import org.w3c.css.sac.InputSource; import org.w3c.dom.css.CSSStyleDeclaration; +import com.commafeed.backend.feed.FeedEntryKeyword.Mode; import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedSubscription; import com.commafeed.frontend.model.Entry; @@ -495,19 +496,20 @@ public class FeedUtils { return rot13(new String(Base64.decodeBase64(code))); } - public static void removeUnwantedFromSearch(List entries, String keywords) { - if (StringUtils.isBlank(keywords)) { - return; - } - + public static void removeUnwantedFromSearch(List entries, List keywords) { Iterator it = entries.iterator(); while (it.hasNext()) { Entry entry = it.next(); boolean keep = true; - for (String keyword : keywords.split(" ")) { + for (FeedEntryKeyword keyword : keywords) { String title = Jsoup.parse(entry.getTitle()).text(); String content = Jsoup.parse(entry.getContent()).text(); - if (!StringUtils.containsIgnoreCase(content, keyword) && !StringUtils.containsIgnoreCase(title, keyword)) { + boolean condition = !StringUtils.containsIgnoreCase(content, keyword.getKeyword()) + && !StringUtils.containsIgnoreCase(title, keyword.getKeyword()); + if (keyword.getMode() == Mode.EXCLUDE) { + condition = !condition; + } + if (condition) { keep = false; break; } @@ -517,5 +519,4 @@ public class FeedUtils { } } } - } diff --git a/src/main/java/com/commafeed/backend/service/FeedEntryService.java b/src/main/java/com/commafeed/backend/service/FeedEntryService.java index a3c5fbfc..34256775 100644 --- a/src/main/java/com/commafeed/backend/service/FeedEntryService.java +++ b/src/main/java/com/commafeed/backend/service/FeedEntryService.java @@ -12,6 +12,7 @@ import com.commafeed.backend.cache.CacheService; import com.commafeed.backend.dao.FeedEntryDAO; import com.commafeed.backend.dao.FeedEntryStatusDAO; import com.commafeed.backend.dao.FeedSubscriptionDAO; +import com.commafeed.backend.feed.FeedEntryKeyword; import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntryStatus; import com.commafeed.backend.model.FeedSubscription; @@ -65,9 +66,9 @@ public class FeedEntryService { feedEntryStatusDAO.saveOrUpdate(status); } - public void markSubscriptionEntries(User user, List subscriptions, Date olderThan, String keywords) { - List statuses = feedEntryStatusDAO.findBySubscriptions(user, subscriptions, true, keywords, null, -1, -1, null, false, - false, null); + public void markSubscriptionEntries(User user, List subscriptions, Date olderThan, List keywords) { + List statuses = feedEntryStatusDAO.findBySubscriptions(user, subscriptions, true, keywords, null, -1, -1, null, + false, false, null); markList(statuses, olderThan); cache.invalidateUnreadCount(subscriptions.toArray(new FeedSubscription[0])); cache.invalidateUserRootCategory(user); diff --git a/src/main/java/com/commafeed/frontend/resource/CategoryREST.java b/src/main/java/com/commafeed/frontend/resource/CategoryREST.java index 9ad593c4..28893c9c 100644 --- a/src/main/java/com/commafeed/frontend/resource/CategoryREST.java +++ b/src/main/java/com/commafeed/frontend/resource/CategoryREST.java @@ -36,6 +36,7 @@ import com.commafeed.backend.cache.CacheService; import com.commafeed.backend.dao.FeedCategoryDAO; import com.commafeed.backend.dao.FeedEntryStatusDAO; import com.commafeed.backend.dao.FeedSubscriptionDAO; +import com.commafeed.backend.feed.FeedEntryKeyword; import com.commafeed.backend.feed.FeedUtils; import com.commafeed.backend.model.FeedCategory; import com.commafeed.backend.model.FeedEntryStatus; @@ -109,6 +110,7 @@ public class CategoryREST { keywords = StringUtils.trimToNull(keywords); Preconditions.checkArgument(keywords == null || StringUtils.length(keywords) >= 3); + List entryKeywords = FeedEntryKeyword.fromQueryString(keywords); limit = Math.min(limit, 1000); limit = Math.max(0, limit); @@ -135,8 +137,8 @@ public class CategoryREST { entries.setName(Optional.fromNullable(tag).or("All")); List subs = feedSubscriptionDAO.findAll(user); removeExcludedSubscriptions(subs, excludedIds); - List list = feedEntryStatusDAO.findBySubscriptions(user, subs, unreadOnly, keywords, newerThanDate, offset, - limit + 1, order, true, onlyIds, tag); + List list = feedEntryStatusDAO.findBySubscriptions(user, subs, unreadOnly, entryKeywords, newerThanDate, + offset, limit + 1, order, true, onlyIds, tag); for (FeedEntryStatus status : list) { entries.getEntries().add( @@ -158,7 +160,7 @@ public class CategoryREST { List categories = feedCategoryDAO.findAllChildrenCategories(user, parent); List subs = feedSubscriptionDAO.findByCategories(user, categories); removeExcludedSubscriptions(subs, excludedIds); - List list = feedEntryStatusDAO.findBySubscriptions(user, subs, unreadOnly, keywords, newerThanDate, + List list = feedEntryStatusDAO.findBySubscriptions(user, subs, unreadOnly, entryKeywords, newerThanDate, offset, limit + 1, order, true, onlyIds, tag); for (FeedEntryStatus status : list) { @@ -180,7 +182,7 @@ public class CategoryREST { entries.setTimestamp(System.currentTimeMillis()); entries.setIgnoredReadStatus(STARRED.equals(id) || keywords != null || tag != null); - FeedUtils.removeUnwantedFromSearch(entries.getEntries(), keywords); + FeedUtils.removeUnwantedFromSearch(entries.getEntries(), entryKeywords); return Response.ok(entries).build(); } @@ -245,11 +247,12 @@ public class CategoryREST { Date olderThan = req.getOlderThan() == null ? null : new Date(req.getOlderThan()); String keywords = req.getKeywords(); + List entryKeywords = FeedEntryKeyword.fromQueryString(keywords); if (ALL.equals(req.getId())) { List subs = feedSubscriptionDAO.findAll(user); removeExcludedSubscriptions(subs, req.getExcludedSubscriptions()); - feedEntryService.markSubscriptionEntries(user, subs, olderThan, keywords); + feedEntryService.markSubscriptionEntries(user, subs, olderThan, entryKeywords); } else if (STARRED.equals(req.getId())) { feedEntryService.markStarredEntries(user, olderThan); } else { @@ -257,7 +260,7 @@ public class CategoryREST { List categories = feedCategoryDAO.findAllChildrenCategories(user, parent); List subs = feedSubscriptionDAO.findByCategories(user, categories); removeExcludedSubscriptions(subs, req.getExcludedSubscriptions()); - feedEntryService.markSubscriptionEntries(user, subs, olderThan, keywords); + feedEntryService.markSubscriptionEntries(user, subs, olderThan, entryKeywords); } return Response.ok().build(); } diff --git a/src/main/java/com/commafeed/frontend/resource/FeedREST.java b/src/main/java/com/commafeed/frontend/resource/FeedREST.java index cd29b06d..edf4ac8c 100644 --- a/src/main/java/com/commafeed/frontend/resource/FeedREST.java +++ b/src/main/java/com/commafeed/frontend/resource/FeedREST.java @@ -44,6 +44,7 @@ import com.commafeed.backend.cache.CacheService; import com.commafeed.backend.dao.FeedCategoryDAO; import com.commafeed.backend.dao.FeedEntryStatusDAO; import com.commafeed.backend.dao.FeedSubscriptionDAO; +import com.commafeed.backend.feed.FeedEntryKeyword; import com.commafeed.backend.feed.FeedFetcher; import com.commafeed.backend.feed.FeedQueues; import com.commafeed.backend.feed.FeedUtils; @@ -127,6 +128,7 @@ public class FeedREST { keywords = StringUtils.trimToNull(keywords); Preconditions.checkArgument(keywords == null || StringUtils.length(keywords) >= 3); + List entryKeywords = FeedEntryKeyword.fromQueryString(keywords); limit = Math.min(limit, 1000); limit = Math.max(0, limit); @@ -146,8 +148,8 @@ public class FeedREST { entries.setErrorCount(subscription.getFeed().getErrorCount()); entries.setFeedLink(subscription.getFeed().getLink()); - List list = feedEntryStatusDAO.findBySubscriptions(user, Arrays.asList(subscription), unreadOnly, keywords, - newerThanDate, offset, limit + 1, order, true, onlyIds, null); + List list = feedEntryStatusDAO.findBySubscriptions(user, Arrays.asList(subscription), unreadOnly, + entryKeywords, newerThanDate, offset, limit + 1, order, true, onlyIds, null); for (FeedEntryStatus status : list) { entries.getEntries().add( @@ -166,7 +168,7 @@ public class FeedREST { entries.setTimestamp(System.currentTimeMillis()); entries.setIgnoredReadStatus(keywords != null); - FeedUtils.removeUnwantedFromSearch(entries.getEntries(), keywords); + FeedUtils.removeUnwantedFromSearch(entries.getEntries(), entryKeywords); return Response.ok(entries).build(); } @@ -289,10 +291,11 @@ public class FeedREST { Date olderThan = req.getOlderThan() == null ? null : new Date(req.getOlderThan()); String keywords = req.getKeywords(); + List entryKeywords = FeedEntryKeyword.fromQueryString(keywords); FeedSubscription subscription = feedSubscriptionDAO.findById(user, Long.valueOf(req.getId())); if (subscription != null) { - feedEntryService.markSubscriptionEntries(user, Arrays.asList(subscription), olderThan, keywords); + feedEntryService.markSubscriptionEntries(user, Arrays.asList(subscription), olderThan, entryKeywords); } return Response.ok().build(); } From e3dbcac9fb7f16dcc7d10f262c0264f78ace2885 Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 7 Nov 2014 10:59:04 +0100 Subject: [PATCH 019/241] dependencies upgrade --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 43157dd6..e5b60cdc 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ UTF-8 0.8.0-SNAPSHOT 3.0 - 3.5.0 + 3.5.1 1.5.0 @@ -329,7 +329,7 @@ mysql mysql-connector-java - 5.1.33 + 5.1.34 org.postgresql From 9790ba735bcfe5a9851ac6184d9543c154d1b71a Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 7 Nov 2014 15:02:11 +0100 Subject: [PATCH 020/241] facebook favicon fetcher --- .../java/com/commafeed/CommaFeedModule.java | 2 + .../favicon/FacebookFaviconFetcher.java | 79 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 src/main/java/com/commafeed/backend/favicon/FacebookFaviconFetcher.java diff --git a/src/main/java/com/commafeed/CommaFeedModule.java b/src/main/java/com/commafeed/CommaFeedModule.java index 62d3911d..d290f68f 100644 --- a/src/main/java/com/commafeed/CommaFeedModule.java +++ b/src/main/java/com/commafeed/CommaFeedModule.java @@ -13,6 +13,7 @@ import com.commafeed.backend.cache.NoopCacheService; import com.commafeed.backend.cache.RedisCacheService; import com.commafeed.backend.favicon.AbstractFaviconFetcher; import com.commafeed.backend.favicon.DefaultFaviconFetcher; +import com.commafeed.backend.favicon.FacebookFaviconFetcher; import com.commafeed.backend.favicon.YoutubeFaviconFetcher; import com.google.inject.AbstractModule; import com.google.inject.Provides; @@ -40,6 +41,7 @@ public class CommaFeedModule extends AbstractModule { Multibinder multibinder = Multibinder.newSetBinder(binder(), AbstractFaviconFetcher.class); multibinder.addBinding().to(YoutubeFaviconFetcher.class); + multibinder.addBinding().to(FacebookFaviconFetcher.class); multibinder.addBinding().to(DefaultFaviconFetcher.class); } } diff --git a/src/main/java/com/commafeed/backend/favicon/FacebookFaviconFetcher.java b/src/main/java/com/commafeed/backend/favicon/FacebookFaviconFetcher.java new file mode 100644 index 00000000..e42e11da --- /dev/null +++ b/src/main/java/com/commafeed/backend/favicon/FacebookFaviconFetcher.java @@ -0,0 +1,79 @@ +package com.commafeed.backend.favicon; + +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URLEncodedUtils; + +import com.commafeed.backend.HttpGetter; +import com.commafeed.backend.HttpGetter.HttpResult; +import com.commafeed.backend.model.Feed; + +@Slf4j +@RequiredArgsConstructor(onConstructor = @__({ @Inject })) +@Singleton +public class FacebookFaviconFetcher extends AbstractFaviconFetcher { + + private final HttpGetter getter; + + @Override + public byte[] fetch(Feed feed) { + String url = feed.getUrl(); + + if (!url.toLowerCase().contains("www.facebook.com")) { + return null; + } + + String userName = extractUserName(url); + if (userName == null) { + return null; + } + + String iconUrl = String.format("https://graph.facebook.com/%s/picture?type=square&height=16", userName); + + byte[] bytes = null; + String contentType = null; + + try { + log.debug("Getting Facebook user's icon, {}", url); + + HttpResult iconResult = getter.getBinary(iconUrl, TIMEOUT); + bytes = iconResult.getContent(); + contentType = iconResult.getContentType(); + } catch (Exception e) { + log.debug("Failed to retrieve YouTube icon", e); + } + + if (!isValidIconResponse(bytes, contentType)) { + bytes = null; + } + return bytes; + } + + private String extractUserName(String url) { + URI uri = null; + try { + uri = new URI(url); + } catch (URISyntaxException e) { + log.debug("could not parse url", e); + return null; + } + List params = URLEncodedUtils.parse(uri, StandardCharsets.UTF_8.name()); + for (NameValuePair param : params) { + if ("id".equals(param.getName())) { + return param.getValue(); + } + } + return null; + } + +} From 9f7c9c34287b270db2778f6cd1cb4fb523042eaa Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 7 Nov 2014 15:02:38 +0100 Subject: [PATCH 021/241] unused code cleanup --- .../commafeed/backend/favicon/YoutubeFaviconFetcher.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/main/java/com/commafeed/backend/favicon/YoutubeFaviconFetcher.java b/src/main/java/com/commafeed/backend/favicon/YoutubeFaviconFetcher.java index 7998d03d..81e7d3ee 100644 --- a/src/main/java/com/commafeed/backend/favicon/YoutubeFaviconFetcher.java +++ b/src/main/java/com/commafeed/backend/favicon/YoutubeFaviconFetcher.java @@ -50,15 +50,8 @@ public class YoutubeFaviconFetcher extends AbstractFaviconFetcher { if (thumbnails.isEmpty()) { return null; } - String thumbnailUrl = thumbnails.get(0).attr("abs:url"); - int thumbnailStart = thumbnailUrl.indexOf("", thumbnailStart); - if (thumbnailStart != -1) { - thumbnailUrl = thumbnailUrl.substring(thumbnailStart + "here.", "generate_api_key_first" : "Generate an API key in your profile first.", "unsubscribe" : "Unsubscribe", "unsubscribe_confirmation" : "Are you sure you want to unsubscribe from this feed?", diff --git a/src/main/app/sass/generic/_misc.scss b/src/main/app/sass/generic/_misc.scss index a60f5dbe..29fcd431 100644 --- a/src/main/app/sass/generic/_misc.scss +++ b/src/main/app/sass/generic/_misc.scss @@ -43,6 +43,10 @@ label { display: block; } +.pre-wrap { + white-space: pre-wrap; +} + .form-horizontal .control-group { margin-bottom: 10px; } diff --git a/src/main/app/templates/feeds.feed_details.html b/src/main/app/templates/feeds.feed_details.html index 3a173bd6..cee5d685 100644 --- a/src/main/app/templates/feeds.feed_details.html +++ b/src/main/app/templates/feeds.feed_details.html @@ -71,9 +71,10 @@
- -
+ +
+

From 96837f908ec15b643964a1e44bb4fe576b6ddea4 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 10 Nov 2014 09:49:59 +0100 Subject: [PATCH 025/241] refactor into a service --- .../FeedEntryFilteringService.java} | 14 ++++----- .../backend/service/FeedUpdateService.java | 7 ++--- .../commafeed/frontend/resource/FeedREST.java | 7 +++-- .../FeedEntryFilteringServiceTest.java} | 31 +++++++++---------- 4 files changed, 27 insertions(+), 32 deletions(-) rename src/main/java/com/commafeed/backend/{feed/FeedEntryFilter.java => service/FeedEntryFilteringService.java} (88%) rename src/test/java/com/commafeed/backend/{feed/FeedEntryFilterTest.java => service/FeedEntryFilteringServiceTest.java} (56%) diff --git a/src/main/java/com/commafeed/backend/feed/FeedEntryFilter.java b/src/main/java/com/commafeed/backend/service/FeedEntryFilteringService.java similarity index 88% rename from src/main/java/com/commafeed/backend/feed/FeedEntryFilter.java rename to src/main/java/com/commafeed/backend/service/FeedEntryFilteringService.java index f9ca6a5e..b51887e2 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedEntryFilter.java +++ b/src/main/java/com/commafeed/backend/service/FeedEntryFilteringService.java @@ -1,14 +1,13 @@ -package com.commafeed.backend.feed; +package com.commafeed.backend.service; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import lombok.RequiredArgsConstructor; - import org.apache.commons.jexl2.JexlContext; import org.apache.commons.jexl2.JexlEngine; import org.apache.commons.jexl2.JexlException; @@ -25,8 +24,7 @@ import org.jsoup.Jsoup; import com.commafeed.backend.model.FeedEntry; -@RequiredArgsConstructor -public class FeedEntryFilter { +public class FeedEntryFilteringService { private static final JexlEngine ENGINE = initEngine(); @@ -69,9 +67,9 @@ public class FeedEntryFilter { return engine; } - private final String filter; + private ExecutorService executor = Executors.newCachedThreadPool(); - public boolean matchesEntry(FeedEntry entry) throws FeedEntryFilterException { + public boolean filterMatchesEntry(String filter, FeedEntry entry) throws FeedEntryFilterException { if (StringUtils.isBlank(filter)) { return true; } @@ -90,7 +88,7 @@ public class FeedEntryFilter { context.set("url", entry.getUrl().toLowerCase()); Callable callable = script.callable(context); - Future future = Executors.newFixedThreadPool(1).submit(callable); + Future future = executor.submit(callable); Object result = null; try { result = future.get(500, TimeUnit.MILLISECONDS); diff --git a/src/main/java/com/commafeed/backend/service/FeedUpdateService.java b/src/main/java/com/commafeed/backend/service/FeedUpdateService.java index 437268af..c96a21eb 100644 --- a/src/main/java/com/commafeed/backend/service/FeedUpdateService.java +++ b/src/main/java/com/commafeed/backend/service/FeedUpdateService.java @@ -13,13 +13,12 @@ import org.apache.commons.codec.digest.DigestUtils; import com.commafeed.backend.dao.FeedEntryDAO; import com.commafeed.backend.dao.FeedEntryStatusDAO; -import com.commafeed.backend.feed.FeedEntryFilter; -import com.commafeed.backend.feed.FeedEntryFilter.FeedEntryFilterException; import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntryContent; import com.commafeed.backend.model.FeedEntryStatus; import com.commafeed.backend.model.FeedSubscription; +import com.commafeed.backend.service.FeedEntryFilteringService.FeedEntryFilterException; @Slf4j @RequiredArgsConstructor(onConstructor = @__({ @Inject })) @@ -29,6 +28,7 @@ public class FeedUpdateService { private final FeedEntryDAO feedEntryDAO; private final FeedEntryStatusDAO feedEntryStatusDAO; private final FeedEntryContentService feedEntryContentService; + private final FeedEntryFilteringService feedEntryFilteringService; /** * this is NOT thread-safe @@ -49,10 +49,9 @@ public class FeedUpdateService { // if filter does not match the entry, mark it as read for (FeedSubscription sub : subscriptions) { - FeedEntryFilter filter = new FeedEntryFilter(sub.getFilter()); boolean matches = true; try { - matches = filter.matchesEntry(entry); + matches = feedEntryFilteringService.filterMatchesEntry(sub.getFilter(), entry); } catch (FeedEntryFilterException e) { log.error("could not evaluate filter {}", sub.getFilter(), e); } diff --git a/src/main/java/com/commafeed/frontend/resource/FeedREST.java b/src/main/java/com/commafeed/frontend/resource/FeedREST.java index ccc52bcd..61e00406 100644 --- a/src/main/java/com/commafeed/frontend/resource/FeedREST.java +++ b/src/main/java/com/commafeed/frontend/resource/FeedREST.java @@ -44,8 +44,6 @@ import com.commafeed.backend.cache.CacheService; import com.commafeed.backend.dao.FeedCategoryDAO; import com.commafeed.backend.dao.FeedEntryStatusDAO; import com.commafeed.backend.dao.FeedSubscriptionDAO; -import com.commafeed.backend.feed.FeedEntryFilter; -import com.commafeed.backend.feed.FeedEntryFilter.FeedEntryFilterException; import com.commafeed.backend.feed.FeedEntryKeyword; import com.commafeed.backend.feed.FeedFetcher; import com.commafeed.backend.feed.FeedQueues; @@ -62,6 +60,8 @@ import com.commafeed.backend.model.UserSettings.ReadingMode; import com.commafeed.backend.model.UserSettings.ReadingOrder; import com.commafeed.backend.opml.OPMLExporter; import com.commafeed.backend.opml.OPMLImporter; +import com.commafeed.backend.service.FeedEntryFilteringService; +import com.commafeed.backend.service.FeedEntryFilteringService.FeedEntryFilterException; import com.commafeed.backend.service.FeedEntryService; import com.commafeed.backend.service.FeedService; import com.commafeed.backend.service.FeedSubscriptionService; @@ -119,6 +119,7 @@ public class FeedREST { private final FeedService feedService; private final FeedEntryService feedEntryService; private final FeedSubscriptionService feedSubscriptionService; + private final FeedEntryFilteringService feedEntryFilteringService; private final FeedQueues queues; private final OPMLImporter opmlImporter; private final OPMLExporter opmlExporter; @@ -440,7 +441,7 @@ public class FeedREST { Preconditions.checkNotNull(req.getId()); try { - new FeedEntryFilter(req.getFilter()).matchesEntry(TEST_ENTRY); + feedEntryFilteringService.filterMatchesEntry(req.getFilter(), TEST_ENTRY); } catch (FeedEntryFilterException e) { Throwable root = Throwables.getRootCause(e); return Response.status(Status.BAD_REQUEST).entity(root.getMessage()).build(); diff --git a/src/test/java/com/commafeed/backend/feed/FeedEntryFilterTest.java b/src/test/java/com/commafeed/backend/service/FeedEntryFilteringServiceTest.java similarity index 56% rename from src/test/java/com/commafeed/backend/feed/FeedEntryFilterTest.java rename to src/test/java/com/commafeed/backend/service/FeedEntryFilteringServiceTest.java index 869710b6..e7836832 100644 --- a/src/test/java/com/commafeed/backend/feed/FeedEntryFilterTest.java +++ b/src/test/java/com/commafeed/backend/service/FeedEntryFilteringServiceTest.java @@ -1,20 +1,24 @@ -package com.commafeed.backend.feed; +package com.commafeed.backend.service; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import com.commafeed.backend.feed.FeedEntryFilter.FeedEntryFilterException; import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntryContent; +import com.commafeed.backend.service.FeedEntryFilteringService.FeedEntryFilterException; -public class FeedEntryFilterTest { +public class FeedEntryFilteringServiceTest { + + private FeedEntryFilteringService service; private FeedEntry entry; private FeedEntryContent content; @Before public void init() { + service = new FeedEntryFilteringService(); + entry = new FeedEntry(); entry.setUrl("https://github.com/Athou/commafeed"); @@ -28,44 +32,37 @@ public class FeedEntryFilterTest { @Test public void emptyFilterMatchesFilter() throws FeedEntryFilterException { - FeedEntryFilter filter = new FeedEntryFilter(null); - Assert.assertTrue(filter.matchesEntry(entry)); + Assert.assertTrue(service.filterMatchesEntry(null, entry)); } @Test public void blankFilterMatchesFilter() throws FeedEntryFilterException { - FeedEntryFilter filter = new FeedEntryFilter(""); - Assert.assertTrue(filter.matchesEntry(entry)); + Assert.assertTrue(service.filterMatchesEntry("", entry)); } @Test public void simpleExpression() throws FeedEntryFilterException { - FeedEntryFilter filter = new FeedEntryFilter("author eq 'athou'"); - Assert.assertTrue(filter.matchesEntry(entry)); + Assert.assertTrue(service.filterMatchesEntry("author eq 'athou'", entry)); } @Test(expected = FeedEntryFilterException.class) public void newIsDisabled() throws FeedEntryFilterException { - FeedEntryFilter filter = new FeedEntryFilter("null eq new ('java.lang.String', 'athou')"); - filter.matchesEntry(entry); + service.filterMatchesEntry("null eq new ('java.lang.String', 'athou')", entry); } @Test(expected = FeedEntryFilterException.class) public void getClassMethodIsDisabled() throws FeedEntryFilterException { - FeedEntryFilter filter = new FeedEntryFilter("null eq ''.getClass()"); - filter.matchesEntry(entry); + service.filterMatchesEntry("null eq ''.getClass()", entry); } @Test public void dotClassIsDisabled() throws FeedEntryFilterException { - FeedEntryFilter filter = new FeedEntryFilter("null eq ''.class"); - Assert.assertTrue(filter.matchesEntry(entry)); + Assert.assertTrue(service.filterMatchesEntry("null eq ''.class", entry)); } @Test(expected = FeedEntryFilterException.class) public void cannotLoopForever() throws FeedEntryFilterException { - FeedEntryFilter filter = new FeedEntryFilter("while(true) {}"); - filter.matchesEntry(entry); + service.filterMatchesEntry("while(true) {}", entry); } } From 15a24e4e7536a025f269e08d2fe1d1d5c9e60617 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 10 Nov 2014 10:04:29 +0100 Subject: [PATCH 026/241] fix components layout --- src/main/app/templates/feeds.category_details.html | 2 +- src/main/app/templates/feeds.feed_details.html | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/app/templates/feeds.category_details.html b/src/main/app/templates/feeds.category_details.html index d0698c7b..d44441f8 100644 --- a/src/main/app/templates/feeds.category_details.html +++ b/src/main/app/templates/feeds.category_details.html @@ -30,7 +30,7 @@
-
+
{{ 'global.link' | translate }} {{ 'details.generate_api_key_first' | translate }}
diff --git a/src/main/app/templates/feeds.feed_details.html b/src/main/app/templates/feeds.feed_details.html index cee5d685..66f1ebe1 100644 --- a/src/main/app/templates/feeds.feed_details.html +++ b/src/main/app/templates/feeds.feed_details.html @@ -6,13 +6,13 @@
{{ error }}
-
- @@ -50,21 +50,21 @@
-
+
{{sub.nextRefresh|entryDate:('details.queued_for_refresh' | translate) }}
-
+
{{sub.message}}
-
+
{{ 'global.link' | translate }} {{ 'details.generate_api_key_first' | translate }}
@@ -72,7 +72,7 @@
-
+

From 3497b82e8cfa94d37efcd986fbd7f981489527d6 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 10 Nov 2014 10:10:25 +0100 Subject: [PATCH 027/241] liquibase upgrade seems to change checksum here --- src/main/resources/changelogs/db.changelog-1.2.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/changelogs/db.changelog-1.2.xml b/src/main/resources/changelogs/db.changelog-1.2.xml index 8beb4a23..3bbe49c6 100644 --- a/src/main/resources/changelogs/db.changelog-1.2.xml +++ b/src/main/resources/changelogs/db.changelog-1.2.xml @@ -45,6 +45,7 @@ 7:9bf9357b47d8666dc7916f9a318138ad + 7:625f651e4c4d8e0aa9576da291baf6a4 From 3cd42d03f0a87f9854286d1c00b4c9da39527529 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 10 Nov 2014 10:10:41 +0100 Subject: [PATCH 028/241] reduce horizontal form height --- src/main/app/sass/generic/_misc.scss | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/app/sass/generic/_misc.scss b/src/main/app/sass/generic/_misc.scss index 29fcd431..84a1b4b9 100644 --- a/src/main/app/sass/generic/_misc.scss +++ b/src/main/app/sass/generic/_misc.scss @@ -120,19 +120,23 @@ blockquote p { font-size: 14px; } -.btn,.btn-large,.btn-small,.btn-mini { +.form-group { + margin-bottom: 10px; +} + +.btn, .btn-large, .btn-small, .btn-mini { border-top-left-radius: 2px; border-top-right-radius: 2px; border-bottom-left-radius: 2px; border-bottom-right-radius: 2px; } -.btn-group>.btn:first-child,.btn-group>.btn-large:first-child { +.btn-group>.btn:first-child, .btn-group>.btn-large:first-child { border-top-left-radius: 2px; border-bottom-left-radius: 2px; } -.btn-group>.btn:last-child,.btn-group>.btn-large:last-child,.btn-group>.dropdown-toggle +.btn-group>.btn:last-child, .btn-group>.btn-large:last-child, .btn-group>.dropdown-toggle { border-top-right-radius: 2px; border-bottom-right-radius: 2px; From f4c5fd7eb47c13c1a9f61c0f1372f2451fccbe78 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 10 Nov 2014 10:14:19 +0100 Subject: [PATCH 029/241] wrap class cast exceptions --- .../backend/service/FeedEntryFilteringService.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/commafeed/backend/service/FeedEntryFilteringService.java b/src/main/java/com/commafeed/backend/service/FeedEntryFilteringService.java index b51887e2..57d7f22c 100644 --- a/src/main/java/com/commafeed/backend/service/FeedEntryFilteringService.java +++ b/src/main/java/com/commafeed/backend/service/FeedEntryFilteringService.java @@ -99,7 +99,11 @@ public class FeedEntryFilteringService { } catch (TimeoutException e) { throw new FeedEntryFilterException("Took too long evaluating expression " + filter, e); } - return (boolean) result; + try { + return (boolean) result; + } catch (ClassCastException e) { + throw new FeedEntryFilterException(e.getMessage(), e); + } } @SuppressWarnings("serial") From 259b9a90dd4240f83b2cf550b42dc3c5f4122520 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 10 Nov 2014 10:16:15 +0100 Subject: [PATCH 030/241] clarify help text --- src/main/app/i18n/en.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/app/i18n/en.js b/src/main/app/i18n/en.js index f7d01143..d267ec54 100644 --- a/src/main/app/i18n/en.js +++ b/src/main/app/i18n/en.js @@ -99,7 +99,7 @@ "queued_for_refresh" : "Queued for refresh", "feed_url" : "Feed URL", "filtering_expression" : "Filtering expression", - "filtering_expression_help" : "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically.\nAvailable variables are 'title', 'content', 'url' and 'author' and their content is converted to lower case for convenience.\nExample: url.contains('youtube') or (author eq 'athou' and title.contains('github').\nComplete available syntax is available here.", + "filtering_expression_help" : "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically.\nAvailable variables are 'title', 'content', 'url' and 'author' and their content is converted to lower case to ease string comparison.\nExample: url.contains('youtube') or (author eq 'athou' and title.contains('github').\nComplete available syntax is available here.", "generate_api_key_first" : "Generate an API key in your profile first.", "unsubscribe" : "Unsubscribe", "unsubscribe_confirmation" : "Are you sure you want to unsubscribe from this feed?", From 3b68e4f32b8e0d2a24f9780d9cf8c8fdc015e82b Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 10 Nov 2014 10:18:20 +0100 Subject: [PATCH 031/241] changelog update --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 3068cb6e..17a62e19 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ v 2.1.0 - dropwizard upgrade to 0.8.0 - you have to remove the "app.contextPath" setting from your yml file, you can optionnaly use server.applicationContextPath instead + - ability to set filtering expressions for subscriptions to automatically mark new entries as read based on title, content, author or url. - ability to use !keyword or -keyword to exclude a keyword from a search query - facebook feeds now show user favicon instead of facebook favicon v 2.0.3 From d50b712bcaa81b923e48bf5365332ffafc674231 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 10 Nov 2014 10:23:01 +0100 Subject: [PATCH 032/241] commons-codec upgrade --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8fefd3f1..89705edc 100644 --- a/pom.xml +++ b/pom.xml @@ -269,7 +269,7 @@ commons-codec commons-codec - 1.9 + 1.10 org.apache.commons From 691bdb151261a83633c5ed3d9ce8c069f4bd1f4d Mon Sep 17 00:00:00 2001 From: Athou Date: Tue, 11 Nov 2014 08:54:58 +0100 Subject: [PATCH 033/241] force g++ install (fix #673) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 06b359c8..ce356d7f 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ You also need Maven 3.x (and a Java 1.7+ JDK) installed in order to build the ap To install maven and openjdk on Ubuntu, issue the following commands - sudo apt-get install build-essential openjdk-7-jdk maven + sudo apt-get install g++ build-essential openjdk-7-jdk maven # Make sure java7 is the selected java version sudo update-alternatives --config java sudo update-alternatives --config javac From db50d50c19906bb38b6a565a086db64995350f5b Mon Sep 17 00:00:00 2001 From: Athou Date: Tue, 11 Nov 2014 20:45:55 +0100 Subject: [PATCH 034/241] remove dotted lines on focused links --- src/main/app/sass/generic/_misc.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/app/sass/generic/_misc.scss b/src/main/app/sass/generic/_misc.scss index 84a1b4b9..f0cddb0c 100644 --- a/src/main/app/sass/generic/_misc.scss +++ b/src/main/app/sass/generic/_misc.scss @@ -1,3 +1,7 @@ +a:focus { + outline: none; +} + .container-full { width: 100%; margin: 0 auto; From 0fe3afe2544926ff2d9f6f96cbe621924ea30ef7 Mon Sep 17 00:00:00 2001 From: Athou Date: Tue, 11 Nov 2014 20:45:55 +0100 Subject: [PATCH 035/241] remove underline on focused link --- src/main/app/sass/generic/_misc.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/app/sass/generic/_misc.scss b/src/main/app/sass/generic/_misc.scss index f0cddb0c..34bc3b25 100644 --- a/src/main/app/sass/generic/_misc.scss +++ b/src/main/app/sass/generic/_misc.scss @@ -1,5 +1,6 @@ a:focus { outline: none; + text-decoration: none; } .container-full { From e09d7fb103b586d5e775d6f0559888865517182c Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 14 Nov 2014 16:10:06 +0100 Subject: [PATCH 036/241] new dark theme 'nightsky' --- CHANGELOG | 1 + src/main/app/js/controllers.js | 2 +- src/main/app/sass/app.scss | 1 + src/main/app/sass/themes/_nightsky.scss | 109 ++++++++++++++++++++++++ src/main/app/templates/_tags.html | 4 +- src/main/app/templates/_toolbar.html | 4 +- 6 files changed, 116 insertions(+), 5 deletions(-) create mode 100644 src/main/app/sass/themes/_nightsky.scss diff --git a/CHANGELOG b/CHANGELOG index 17a62e19..f9226cd0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ v 2.1.0 - ability to set filtering expressions for subscriptions to automatically mark new entries as read based on title, content, author or url. - ability to use !keyword or -keyword to exclude a keyword from a search query - facebook feeds now show user favicon instead of facebook favicon + - new dark theme 'nightsky' v 2.0.3 - internet explorer ajax cache workaround - categories are now deletable again diff --git a/src/main/app/js/controllers.js b/src/main/app/js/controllers.js index 9eb1747d..aa360db3 100644 --- a/src/main/app/js/controllers.js +++ b/src/main/app/js/controllers.js @@ -1371,7 +1371,7 @@ module.controller('SettingsCtrl', ['$scope', '$location', 'SettingsService', 'An $scope.langs = LangService.langs; - $scope.themes = ['default', 'bootstrap', 'dark', 'ebraminio', 'MRACHINI', 'svetla', 'third']; + $scope.themes = ['default', 'bootstrap', 'dark', 'ebraminio', 'MRACHINI', 'nightsky', 'svetla', 'third']; $scope.settingsService = SettingsService; $scope.$watch('settingsService.settings', function(value) { diff --git a/src/main/app/sass/app.scss b/src/main/app/sass/app.scss index aaa20831..bd30232f 100644 --- a/src/main/app/sass/app.scss +++ b/src/main/app/sass/app.scss @@ -17,6 +17,7 @@ @import "themes/bootstrap"; @import "themes/ebraminio"; @import "themes/MRACHINI"; +@import "themes/nightsky"; @import "themes/svetla"; @import "themes/dark"; @import "themes/third"; diff --git a/src/main/app/sass/themes/_nightsky.scss b/src/main/app/sass/themes/_nightsky.scss new file mode 100644 index 00000000..0a46a34e --- /dev/null +++ b/src/main/app/sass/themes/_nightsky.scss @@ -0,0 +1,109 @@ +#theme-nightsky { + a { + color: #2A9FD6; + } + + .nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus { + color: #FFF; + background-color: #2A9FD6; + } + + body, .toolbar { + color: #C6C6C6; + background-color: #2F2F2F; + } + + .btn-default { + color: #C6C6C6; + background-color: #424242; + border-color: #424242; + } + + .btn-default:hover, .btn-default:focus, .btn-default.focus, .btn-default:active, + .btn-default.active, .open>.dropdown-toggle.btn-default { + background-color: #282828; + border-color: #232323; + } + + .css-treeview li .tree-item:hover { + background-color: #282828; + } + + .css-treeview .unread-counter { + color: #C8C8C8; + } + + .css-treeview a { + color: #C6C6C6; + } + + .css-treeview .selected { + color: #C00; + } + + .entrylist-header { + border-bottom: 1px solid #282828; + } + + #feed-accordion .entry { + border-bottom: 1px solid #282828; + } + + #feed-accordion .entry-body .entry-title { + font-weight: normal; + } + + #feed-accordion .unread .entry-heading .entry-name { + font-weight: normal; + } + + #feed-accordion .unread .entry-heading { + background-color: #444; + } + + #feed-accordion .entry-heading { + background-color: #2F2F2F; + } + + #feed-accordion .unread .entry-heading:hover { + background-color: #2F2F2F; + } + + #feed-accordion .current.closed .entry-heading { + background-color: #151515; + } + + #feed-accordion .entry-heading-link { + color: #C6C6C6; + } + + #feed-accordion .entry-external-link { + color: #C6C6C6; + } + + #feed-accordion .icon-star-empty { + color: #C6C6C6; + } + + #feed-accordion .entry-heading .feed-name { + color: #939393; + } + + #feed-accordion .entry-body-content { + color: #C6C6C6; + } + + #feed-accordion .entry-buttons { + background-color: #494949; + border-top: 1px solid #282828; + } + + #feed-accordion .share-buttons a { + color: #C6C6C6; + } + + #feed-accordion a.mark-up-to { + color: #C6C6C6; + } +} + diff --git a/src/main/app/templates/_tags.html b/src/main/app/templates/_tags.html index 2c59e872..dd1d20e4 100644 --- a/src/main/app/templates/_tags.html +++ b/src/main/app/templates/_tags.html @@ -1,8 +1,8 @@ - + {{ 'global.tags' | translate }} - + {{tag}} diff --git a/src/main/app/templates/_toolbar.html b/src/main/app/templates/_toolbar.html index e6ce7707..d59c3d8f 100644 --- a/src/main/app/templates/_toolbar.html +++ b/src/main/app/templates/_toolbar.html @@ -124,9 +124,9 @@
From b5dfd371d9cc04ba32a5332c97e967351c8999e2 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 17 Nov 2014 06:44:11 +0100 Subject: [PATCH 037/241] typo --- .../com/commafeed/backend/favicon/FacebookFaviconFetcher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/commafeed/backend/favicon/FacebookFaviconFetcher.java b/src/main/java/com/commafeed/backend/favicon/FacebookFaviconFetcher.java index e42e11da..092fe0cb 100644 --- a/src/main/java/com/commafeed/backend/favicon/FacebookFaviconFetcher.java +++ b/src/main/java/com/commafeed/backend/favicon/FacebookFaviconFetcher.java @@ -50,7 +50,7 @@ public class FacebookFaviconFetcher extends AbstractFaviconFetcher { bytes = iconResult.getContent(); contentType = iconResult.getContentType(); } catch (Exception e) { - log.debug("Failed to retrieve YouTube icon", e); + log.debug("Failed to retrieve Facebook icon", e); } if (!isValidIconResponse(bytes, contentType)) { From 12bcbfa9f7e916bf7865879de48d728fed846ffd Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 17 Nov 2014 14:24:16 +0100 Subject: [PATCH 038/241] angular upgrade --- bower.json | 22 +++++++++++++--------- src/main/app/js/main.js | 17 +++++++---------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/bower.json b/bower.json index 9e866faa..3691e1a5 100644 --- a/bower.json +++ b/bower.json @@ -8,19 +8,19 @@ "lodash": "2.4.1", "bootstrap": "3.1.1", "font-awesome": "3.2.1", - "angular": "1.2.16", - "angular-resource": "1.2.16", - "angular-route": "1.2.16", - "angular-sanitize": "1.2.16", - "angular-touch": "1.2.16", - "angular-animate": "1.2.16", + "angular": "1.3.2", + "angular-resource": "1.3.2", + "angular-route": "1.3.2", + "angular-sanitize": "1.3.2", + "angular-touch": "1.3.2", + "angular-animate": "1.3.2", "angular-ui-router": "0.2.8", "angular-ui-utils": "0.1.0", "angular-ui-select2": "0.0.5", "angular-bootstrap": "0.2.0", - "angular-loading-bar": "0.5.0", - "angular-translate": "2.2.0", - "angular-translate-loader-static-files": "2.2.0", + "angular-loading-bar": "0.6.0", + "angular-translate": "2.4.2", + "angular-translate-loader-static-files": "2.4.2", "ngInfiniteScroll": "1.0.0", "ng-grid": "2.0.6", "mousetrap": "1.4.6", @@ -29,5 +29,9 @@ "readabilicons": "arc90/readability-readabilicons#34c55561c5b8ec6e90714b50237c06b13cb9d59c", "zocial": "samcollins/css-social-buttons#1f59ecacde475e563fb6771667597493ec4eecb6", "swagger-ui": "2.0.21" + }, + "resolutions": { + "angular": "1.3.2", + "angular-translate": "2.4.2" } } diff --git a/src/main/app/js/main.js b/src/main/app/js/main.js index bb668c49..8a51e0d0 100644 --- a/src/main/app/js/main.js +++ b/src/main/app/js/main.js @@ -23,26 +23,23 @@ app.config([ $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|javascript):/); var interceptor = ['$rootScope', '$q', '$injector', function(scope, $q, $injector) { - - var success = function(response) { + var f = {}; + + f.response = function(response) { return response; }; - var error = function(response) { + + f.responseError = function(response) { var status = response.status; if (status == 401) { $injector.get('$state').transitionTo('welcome'); } return $q.reject(response); }; - - var promise = function(promise) { - return promise.then(success, error); - }; - - return promise; + return f; }]; - $httpProvider.responseInterceptors.push(interceptor); + $httpProvider.interceptors.push(interceptor); $stateProvider.state('feeds', { 'abstract' : true, From 6812bf2388d038e768cd741657aa83eebe4b01bc Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 17 Nov 2014 14:24:27 +0100 Subject: [PATCH 039/241] jquery upgrade --- bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bower.json b/bower.json index 3691e1a5..8e7fbf71 100644 --- a/bower.json +++ b/bower.json @@ -2,7 +2,7 @@ "name": "commafeed", "version": "2.0.0", "dependencies": { - "jquery": "1.11.0", + "jquery": "2.1.1", "jquery-ui": "1.10.3", "jquery-mousewheel": "3.1.12", "lodash": "2.4.1", From 0dd7c777eed6edf88de305012565526ead222c52 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 17 Nov 2014 14:24:32 +0100 Subject: [PATCH 040/241] bootstrap upgrade --- bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bower.json b/bower.json index 8e7fbf71..60c1dafe 100644 --- a/bower.json +++ b/bower.json @@ -6,7 +6,7 @@ "jquery-ui": "1.10.3", "jquery-mousewheel": "3.1.12", "lodash": "2.4.1", - "bootstrap": "3.1.1", + "bootstrap": "3.3.1", "font-awesome": "3.2.1", "angular": "1.3.2", "angular-resource": "1.3.2", From 548fb7099b6be4afe852c6083a5496315bfb936d Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 17 Nov 2014 14:28:24 +0100 Subject: [PATCH 041/241] ui router upgrade --- bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bower.json b/bower.json index 60c1dafe..6a4d2536 100644 --- a/bower.json +++ b/bower.json @@ -14,7 +14,7 @@ "angular-sanitize": "1.3.2", "angular-touch": "1.3.2", "angular-animate": "1.3.2", - "angular-ui-router": "0.2.8", + "angular-ui-router": "0.2.12", "angular-ui-utils": "0.1.0", "angular-ui-select2": "0.0.5", "angular-bootstrap": "0.2.0", From 253507d14b0f46e5c34d59b0ff41d3cd67dd9a55 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 17 Nov 2014 14:37:48 +0100 Subject: [PATCH 042/241] momentjs upgrade --- bower.json | 2 +- src/main/app/index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 6a4d2536..e44e74dd 100644 --- a/bower.json +++ b/bower.json @@ -24,7 +24,7 @@ "ngInfiniteScroll": "1.0.0", "ng-grid": "2.0.6", "mousetrap": "1.4.6", - "momentjs": "2.6.0", + "momentjs": "2.8.3", "device.js": "matthewhudson/device.js#2ae5c775e35ccc837589e5af34e292c54936778c", "readabilicons": "arc90/readability-readabilicons#34c55561c5b8ec6e90714b50237c06b13cb9d59c", "zocial": "samcollins/css-social-buttons#1f59ecacde475e563fb6771667597493ec4eecb6", diff --git a/src/main/app/index.html b/src/main/app/index.html index 8ba8490c..63021056 100644 --- a/src/main/app/index.html +++ b/src/main/app/index.html @@ -62,7 +62,7 @@ - + From 5d2378f291913abdb489db782058ea4f0d415a61 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 17 Nov 2014 14:44:05 +0100 Subject: [PATCH 043/241] swagger ui upgrade --- bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bower.json b/bower.json index e44e74dd..b08b6435 100644 --- a/bower.json +++ b/bower.json @@ -28,7 +28,7 @@ "device.js": "matthewhudson/device.js#2ae5c775e35ccc837589e5af34e292c54936778c", "readabilicons": "arc90/readability-readabilicons#34c55561c5b8ec6e90714b50237c06b13cb9d59c", "zocial": "samcollins/css-social-buttons#1f59ecacde475e563fb6771667597493ec4eecb6", - "swagger-ui": "2.0.21" + "swagger-ui": "2.0.24" }, "resolutions": { "angular": "1.3.2", From 87bcaa4731e680da146d7bd76cb6334918865061 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 17 Nov 2014 15:14:59 +0100 Subject: [PATCH 044/241] nightsky theme tweaks --- src/main/app/sass/themes/_nightsky.scss | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/app/sass/themes/_nightsky.scss b/src/main/app/sass/themes/_nightsky.scss index 0a46a34e..288d6169 100644 --- a/src/main/app/sass/themes/_nightsky.scss +++ b/src/main/app/sass/themes/_nightsky.scss @@ -30,10 +30,14 @@ } .css-treeview .unread-counter { - color: #C8C8C8; + color: #939393; } - .css-treeview a { + .css-treeview .category-link, .css-treeview a { + color: #939393; + } + + .css-treeview a .unread { color: #C6C6C6; } @@ -53,8 +57,13 @@ font-weight: normal; } + #feed-accordion .entry-heading .entry-name { + color: #939393; + } + #feed-accordion .unread .entry-heading .entry-name { font-weight: normal; + color: #C6C6C6; } #feed-accordion .unread .entry-heading { From 6d4d2c3e7efa825ea4a120b0b992e8027cd4e2a9 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 17 Nov 2014 16:05:19 +0100 Subject: [PATCH 045/241] lang() is deprecated --- src/main/app/js/services.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/app/js/services.js b/src/main/app/js/services.js index f9cb5306..c4807b4d 100644 --- a/src/main/app/js/services.js +++ b/src/main/app/js/services.js @@ -62,7 +62,7 @@ module.factory('SettingsService', ['$resource', '$translate', function($resource } else if (lang === 'ms') { lang = 'ms-my'; } - moment.lang(lang, {}); + moment.locale(lang, {}); if (callback) { callback(data); } From 2aef4e5d05c99100db898bb38d9fc98f3dbbc350 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 17 Nov 2014 16:06:31 +0100 Subject: [PATCH 046/241] typo --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index f9226cd0..0f5c2e68 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,6 @@ v 2.1.0 - dropwizard upgrade to 0.8.0 - - you have to remove the "app.contextPath" setting from your yml file, you can optionnaly use server.applicationContextPath instead + - you have to remove the "app.contextPath" setting from your yml file, you can optionally use server.applicationContextPath instead - ability to set filtering expressions for subscriptions to automatically mark new entries as read based on title, content, author or url. - ability to use !keyword or -keyword to exclude a keyword from a search query - facebook feeds now show user favicon instead of facebook favicon From 6819d5aa8b6206a330b72a4527df845c1811a226 Mon Sep 17 00:00:00 2001 From: Athou Date: Tue, 18 Nov 2014 12:13:13 +0100 Subject: [PATCH 047/241] highlight unread categories --- src/main/app/sass/themes/_nightsky.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/app/sass/themes/_nightsky.scss b/src/main/app/sass/themes/_nightsky.scss index 288d6169..a15578e8 100644 --- a/src/main/app/sass/themes/_nightsky.scss +++ b/src/main/app/sass/themes/_nightsky.scss @@ -37,7 +37,7 @@ color: #939393; } - .css-treeview a .unread { + .css-treeview a .unread, .css-treeview .category-link .unread { color: #C6C6C6; } From 81850acdfe32eaedf0bed92e0ce382ac7a7d650b Mon Sep 17 00:00:00 2001 From: Athou Date: Tue, 18 Nov 2014 12:24:36 +0100 Subject: [PATCH 048/241] enable livereload --- gulpfile.js | 3 ++- package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index e7d273fc..9eca9a04 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -72,7 +72,7 @@ gulp.task('build-dev', ['images', 'i18n', 'favicons', 'sass', 'fonts', 'select2' var jsFilter = filter("**/*.js"); var cssFilter = filter("**/*.css"); return gulp.src([SRC_DIR + 'index.html', TEMP_DIR + 'app.css']).pipe(assets).pipe(rev()).pipe(assets.restore()).pipe(useref()).pipe( - revReplace()).pipe(gulp.dest(BUILD_DIR)); + revReplace()).pipe(gulp.dest(BUILD_DIR)).pipe(connect.reload()); }); gulp.task('build', ['images', 'i18n', 'favicons', 'sass', 'fonts', 'select2', 'swagger-ui', 'template-cache', 'bower'], function() { @@ -101,6 +101,7 @@ gulp.task('serve', function() { connect.server({ root : BUILD_DIR, port : 8082, + livereload: true, middleware : function() { var rest = '^/rest/(.*)$ http://localhost:8083/rest/$1 [P]'; var next = '^/next(.*)$ http://localhost:8083/next$1 [P]'; diff --git a/package.json b/package.json index ec24705a..158ad8af 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "gulp-uglify": "0.3.1", "gulp-filter": "1.0.0", "gulp-bower": "0.0.6", - "gulp-connect": "2.0.6", + "gulp-connect": "2.2.0", "connect-modrewrite": "0.7.7", "gulp-sass": "0.7.2", "gulp-useref": "0.6.0", From f40630aced4b54d62aa6e0aa6983e9f7cf610263 Mon Sep 17 00:00:00 2001 From: Athou Date: Tue, 18 Nov 2014 12:43:23 +0100 Subject: [PATCH 049/241] upgrade dev dependencies --- package.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 158ad8af..037779af 100644 --- a/package.json +++ b/package.json @@ -4,17 +4,17 @@ "main": "main.js", "private": true, "devDependencies": { - "gulp": "3.8.7", - "gulp-rev": "1.0.0", - "gulp-rev-replace": "0.3.0", - "gulp-minify-css": "0.3.7", - "gulp-uglify": "0.3.1", - "gulp-filter": "1.0.0", - "gulp-bower": "0.0.6", + "gulp": "3.8.10", + "gulp-rev": "2.0.1", + "gulp-rev-replace": "0.3.1", + "gulp-minify-css": "0.3.11", + "gulp-uglify": "1.0.1", + "gulp-filter": "1.0.2", + "gulp-bower": "0.0.7", "gulp-connect": "2.2.0", - "connect-modrewrite": "0.7.7", - "gulp-sass": "0.7.2", - "gulp-useref": "0.6.0", - "gulp-angular-templatecache": "1.3.0" + "connect-modrewrite": "0.7.9", + "gulp-sass": "1.1.0", + "gulp-useref": "1.0.2", + "gulp-angular-templatecache": "1.4.2" } } From 5ea92a7d185584a1c1a15638e8605984221d8af0 Mon Sep 17 00:00:00 2001 From: Athou Date: Tue, 18 Nov 2014 17:27:06 +0100 Subject: [PATCH 050/241] jedis upgrade --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 89705edc..02845c26 100644 --- a/pom.xml +++ b/pom.xml @@ -280,7 +280,7 @@ redis.clients jedis - 2.6.0 + 2.6.1 com.sun.mail From 81481c37fed2ebbdddb1e380a7dbe9b870f290f0 Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 20 Nov 2014 12:26:48 +0100 Subject: [PATCH 051/241] use daemon threads --- src/main/java/com/commafeed/CommaFeedApplication.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/commafeed/CommaFeedApplication.java b/src/main/java/com/commafeed/CommaFeedApplication.java index 13aff766..33680dee 100644 --- a/src/main/java/com/commafeed/CommaFeedApplication.java +++ b/src/main/java/com/commafeed/CommaFeedApplication.java @@ -137,7 +137,7 @@ public class CommaFeedApplication extends Application { environment.servlets().addServlet("analytics.js", injector.getInstance(AnalyticsServlet.class)).addMapping("/analytics.js"); // Scheduled tasks - ScheduledExecutorService executor = environment.lifecycle().scheduledExecutorService("task-scheduler").build(); + ScheduledExecutorService executor = environment.lifecycle().scheduledExecutorService("task-scheduler", true).build(); injector.getInstance(OldStatusesCleanupTask.class).register(executor); injector.getInstance(OrphansCleanupTask.class).register(executor); From e6050219bc701449cb94a5387de1b8dd989c2909 Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 20 Nov 2014 14:50:24 +0100 Subject: [PATCH 052/241] log runtime exceptions --- .../com/commafeed/backend/feed/FeedRefreshExecutor.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/commafeed/backend/feed/FeedRefreshExecutor.java b/src/main/java/com/commafeed/backend/feed/FeedRefreshExecutor.java index b1f7f597..75f42705 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedRefreshExecutor.java +++ b/src/main/java/com/commafeed/backend/feed/FeedRefreshExecutor.java @@ -37,7 +37,14 @@ public class FeedRefreshExecutor { return offerLast(r); } } - }); + }) { + @Override + protected void afterExecute(Runnable r, Throwable t) { + if (t != null) { + log.error("thread from pool {} threw a runtime exception", poolName, t); + } + }; + }; pool.setRejectedExecutionHandler(new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { From 10461941d77aa10efdd597a41dfa8e77511f12eb Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 21 Nov 2014 10:01:34 +0100 Subject: [PATCH 053/241] fix nightsky loadingbar color --- src/main/app/sass/themes/_nightsky.scss | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/app/sass/themes/_nightsky.scss b/src/main/app/sass/themes/_nightsky.scss index a15578e8..0096d076 100644 --- a/src/main/app/sass/themes/_nightsky.scss +++ b/src/main/app/sass/themes/_nightsky.scss @@ -114,5 +114,13 @@ #feed-accordion a.mark-up-to { color: #C6C6C6; } + + #loading-bar .bar { + background: #C6C6C6; + } + + #loading-bar .peg { + box-shadow: 0 0 10px #C6C6C6, 0 0 5px #C6C6C6; + } } From 18aa2fcd925417f070682c52aea7f6bae052aea2 Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 21 Nov 2014 10:06:48 +0100 Subject: [PATCH 054/241] fix subscription error handling --- src/main/java/com/commafeed/frontend/resource/FeedREST.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/commafeed/frontend/resource/FeedREST.java b/src/main/java/com/commafeed/frontend/resource/FeedREST.java index 61e00406..33cd929e 100644 --- a/src/main/java/com/commafeed/frontend/resource/FeedREST.java +++ b/src/main/java/com/commafeed/frontend/resource/FeedREST.java @@ -268,7 +268,7 @@ public class FeedREST { info = fetchFeedInternal(req.getUrl()); } catch (Exception e) { return Response.status(Status.INTERNAL_SERVER_ERROR).entity(Throwables.getStackTraceAsString(Throwables.getRootCause(e))) - .build(); + .type(MediaType.TEXT_PLAIN).build(); } return Response.ok(info).build(); } From 0313c5c5606dd7dd401f0d3f447b47b6f578a6b0 Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 21 Nov 2014 12:04:59 +0100 Subject: [PATCH 055/241] fix placeholder display --- src/main/app/templates/_feedsearch.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/app/templates/_feedsearch.html b/src/main/app/templates/_feedsearch.html index 2c14722f..2f87e9ed 100644 --- a/src/main/app/templates/_feedsearch.html +++ b/src/main/app/templates/_feedsearch.html @@ -6,7 +6,7 @@

{{ 'feedsearch.help' | translate }} From cbc792d4065f47647da4bf124fa03796cf6ef79e Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 21 Nov 2014 16:50:20 +0100 Subject: [PATCH 056/241] use the old id generator as it's the one we were using before dropwizard --- src/main/java/com/commafeed/CommaFeedApplication.java | 9 +++++++-- src/main/java/com/commafeed/CommaFeedConfiguration.java | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/commafeed/CommaFeedApplication.java b/src/main/java/com/commafeed/CommaFeedApplication.java index 33680dee..d5c9410d 100644 --- a/src/main/java/com/commafeed/CommaFeedApplication.java +++ b/src/main/java/com/commafeed/CommaFeedApplication.java @@ -24,6 +24,7 @@ import javax.servlet.http.HttpServletRequest; import org.eclipse.jetty.server.session.SessionHandler; import org.glassfish.jersey.media.multipart.MultiPartFeature; +import org.hibernate.cfg.AvailableSettings; import com.commafeed.backend.feed.FeedRefreshTaskGiver; import com.commafeed.backend.feed.FeedRefreshUpdater; @@ -89,14 +90,18 @@ public class CommaFeedApplication extends Application { FeedSubscription.class, User.class, UserRole.class, UserSettings.class) { @Override public DataSourceFactory getDataSourceFactory(CommaFeedConfiguration configuration) { - return configuration.getDatabase(); + DataSourceFactory factory = configuration.getDataSourceFactory(); + + // keep using old id generator for backward compatibility + factory.getProperties().put(AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, "false"); + return factory; } }); bootstrap.addBundle(new MigrationsBundle() { @Override public DataSourceFactory getDataSourceFactory(CommaFeedConfiguration configuration) { - return configuration.getDatabase(); + return configuration.getDataSourceFactory(); } }); diff --git a/src/main/java/com/commafeed/CommaFeedConfiguration.java b/src/main/java/com/commafeed/CommaFeedConfiguration.java index 8b41b4de..640a1a0d 100644 --- a/src/main/java/com/commafeed/CommaFeedConfiguration.java +++ b/src/main/java/com/commafeed/CommaFeedConfiguration.java @@ -35,7 +35,7 @@ public class CommaFeedConfiguration extends Configuration { @Valid @NotNull @JsonProperty("database") - private DataSourceFactory database = new DataSourceFactory(); + private DataSourceFactory dataSourceFactory = new DataSourceFactory(); @Valid @NotNull From d1be331f994eb9ca011bc638374787d0e22afde9 Mon Sep 17 00:00:00 2001 From: Athou Date: Sat, 22 Nov 2014 11:53:46 +0100 Subject: [PATCH 057/241] handle nulls correctly --- .../backend/service/FeedEntryFilteringService.java | 9 +++++---- .../backend/service/FeedEntryFilteringServiceTest.java | 6 ++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/commafeed/backend/service/FeedEntryFilteringService.java b/src/main/java/com/commafeed/backend/service/FeedEntryFilteringService.java index 57d7f22c..1b866acb 100644 --- a/src/main/java/com/commafeed/backend/service/FeedEntryFilteringService.java +++ b/src/main/java/com/commafeed/backend/service/FeedEntryFilteringService.java @@ -82,10 +82,11 @@ public class FeedEntryFilteringService { } JexlContext context = new MapContext(); - context.set("title", Jsoup.parse(entry.getContent().getTitle()).text().toLowerCase()); - context.set("author", entry.getContent().getAuthor().toLowerCase()); - context.set("content", Jsoup.parse(entry.getContent().getContent()).text().toLowerCase()); - context.set("url", entry.getUrl().toLowerCase()); + context.set("title", entry.getContent().getTitle() == null ? "" : Jsoup.parse(entry.getContent().getTitle()).text().toLowerCase()); + context.set("author", entry.getContent().getAuthor() == null ? "" : entry.getContent().getAuthor().toLowerCase()); + context.set("content", entry.getContent().getContent() == null ? "" : Jsoup.parse(entry.getContent().getContent()).text() + .toLowerCase()); + context.set("url", entry.getUrl() == null ? "" : entry.getUrl().toLowerCase()); Callable callable = script.callable(context); Future future = executor.submit(callable); diff --git a/src/test/java/com/commafeed/backend/service/FeedEntryFilteringServiceTest.java b/src/test/java/com/commafeed/backend/service/FeedEntryFilteringServiceTest.java index e7836832..8babc731 100644 --- a/src/test/java/com/commafeed/backend/service/FeedEntryFilteringServiceTest.java +++ b/src/test/java/com/commafeed/backend/service/FeedEntryFilteringServiceTest.java @@ -65,4 +65,10 @@ public class FeedEntryFilteringServiceTest { service.filterMatchesEntry("while(true) {}", entry); } + @Test + public void handlesNullCorrectly() throws FeedEntryFilterException { + entry.setContent(new FeedEntryContent()); + service.filterMatchesEntry("author eq 'athou'", entry); + } + } From a477c9fa6daed6421870dcde61668c966536f7a8 Mon Sep 17 00:00:00 2001 From: Athou Date: Sat, 22 Nov 2014 11:53:53 +0100 Subject: [PATCH 058/241] fix error display --- src/main/java/com/commafeed/frontend/resource/FeedREST.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/commafeed/frontend/resource/FeedREST.java b/src/main/java/com/commafeed/frontend/resource/FeedREST.java index 33cd929e..4253e2b3 100644 --- a/src/main/java/com/commafeed/frontend/resource/FeedREST.java +++ b/src/main/java/com/commafeed/frontend/resource/FeedREST.java @@ -444,7 +444,7 @@ public class FeedREST { feedEntryFilteringService.filterMatchesEntry(req.getFilter(), TEST_ENTRY); } catch (FeedEntryFilterException e) { Throwable root = Throwables.getRootCause(e); - return Response.status(Status.BAD_REQUEST).entity(root.getMessage()).build(); + return Response.status(Status.BAD_REQUEST).entity(root.getMessage()).type(MediaType.TEXT_PLAIN).build(); } FeedSubscription subscription = feedSubscriptionDAO.findById(user, req.getId()); From 4684e43f42b74c69615b52e82ec4e7ff9c0a9d1f Mon Sep 17 00:00:00 2001 From: Athou Date: Sat, 22 Nov 2014 22:29:56 +0100 Subject: [PATCH 059/241] relax opml import conditions (#677) --- src/main/java/com/commafeed/backend/rome/OPML11Parser.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/commafeed/backend/rome/OPML11Parser.java b/src/main/java/com/commafeed/backend/rome/OPML11Parser.java index 995baa1b..472c5b4c 100644 --- a/src/main/java/com/commafeed/backend/rome/OPML11Parser.java +++ b/src/main/java/com/commafeed/backend/rome/OPML11Parser.java @@ -19,8 +19,7 @@ public class OPML11Parser extends OPML10Parser { public boolean isMyType(Document document) { Element e = document.getRootElement(); - if (e.getName().equals("opml") && (e.getChild("head") == null || e.getChild("head").getChild("docs") == null) - && (e.getAttributeValue("version") == null || e.getAttributeValue("version").equals("1.1"))) { + if (e.getName().equals("opml")) { return true; } From 6419d2948938af937c54caae7754092af84f5451 Mon Sep 17 00:00:00 2001 From: Athou Date: Sun, 23 Nov 2014 13:27:34 +0100 Subject: [PATCH 060/241] demo account creation is now skipped by default --- config.yml.example | 3 +++ src/main/java/com/commafeed/CommaFeedConfiguration.java | 2 ++ .../java/com/commafeed/backend/service/StartupService.java | 6 +++++- src/main/java/com/commafeed/frontend/resource/UserREST.java | 5 +++-- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/config.yml.example b/config.yml.example index 77b717cd..631b2300 100644 --- a/config.yml.example +++ b/config.yml.example @@ -7,6 +7,9 @@ app: # wether to allow user registrations allowRegistrations: false + # create a demo account the first time the app starts + createDemoAccount: false + # put your google analytics tracking code here googleAnalyticsTrackingCode: diff --git a/src/main/java/com/commafeed/CommaFeedConfiguration.java b/src/main/java/com/commafeed/CommaFeedConfiguration.java index 640a1a0d..7ff5c3ac 100644 --- a/src/main/java/com/commafeed/CommaFeedConfiguration.java +++ b/src/main/java/com/commafeed/CommaFeedConfiguration.java @@ -69,6 +69,8 @@ public class CommaFeedConfiguration extends Configuration { @NotNull private boolean allowRegistrations; + private boolean createDemoAccount; + private String googleAnalyticsTrackingCode; @NotNull diff --git a/src/main/java/com/commafeed/backend/service/StartupService.java b/src/main/java/com/commafeed/backend/service/StartupService.java index ff3e95b8..685f21bc 100644 --- a/src/main/java/com/commafeed/backend/service/StartupService.java +++ b/src/main/java/com/commafeed/backend/service/StartupService.java @@ -26,6 +26,7 @@ import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.internal.SessionFactoryImpl; import com.commafeed.CommaFeedApplication; +import com.commafeed.CommaFeedConfiguration; import com.commafeed.backend.dao.UnitOfWork; import com.commafeed.backend.dao.UserDAO; import com.commafeed.backend.model.UserRole.Role; @@ -38,6 +39,7 @@ public class StartupService implements Managed { private final SessionFactory sessionFactory; private final UserDAO userDAO; private final UserService userService; + private final CommaFeedConfiguration config; @Override public void start() throws Exception { @@ -95,7 +97,9 @@ public class StartupService implements Managed { try { userService.register(CommaFeedApplication.USERNAME_ADMIN, "admin", "admin@commafeed.com", Arrays.asList(Role.ADMIN, Role.USER), true); - userService.register(CommaFeedApplication.USERNAME_DEMO, "demo", "demo@commafeed.com", Arrays.asList(Role.USER), true); + if (config.getApplicationSettings().isCreateDemoAccount()) { + userService.register(CommaFeedApplication.USERNAME_DEMO, "demo", "demo@commafeed.com", Arrays.asList(Role.USER), true); + } } catch (Exception e) { log.error(e.getMessage(), e); } diff --git a/src/main/java/com/commafeed/frontend/resource/UserREST.java b/src/main/java/com/commafeed/frontend/resource/UserREST.java index 54c9da3d..83466694 100644 --- a/src/main/java/com/commafeed/frontend/resource/UserREST.java +++ b/src/main/java/com/commafeed/frontend/resource/UserREST.java @@ -249,7 +249,7 @@ public class UserREST { sessionHelper.setLoggedInUser(user.get()); return Response.ok().build(); } else { - return Response.status(Response.Status.UNAUTHORIZED).entity("wrong username or password").build(); + return Response.status(Response.Status.UNAUTHORIZED).entity("wrong username or password").type(MediaType.TEXT_PLAIN).build(); } } @@ -270,7 +270,8 @@ public class UserREST { return Response.ok().build(); } catch (Exception e) { log.error(e.getMessage(), e); - return Response.status(Status.INTERNAL_SERVER_ERROR).entity("could not send email: " + e.getMessage()).build(); + return Response.status(Status.INTERNAL_SERVER_ERROR).entity("could not send email: " + e.getMessage()) + .type(MediaType.TEXT_PLAIN).build(); } } From 9ac4187aa83637264bc35a965c5ec727b9aa94c0 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 24 Nov 2014 12:46:43 +0100 Subject: [PATCH 061/241] let the module decide what tasks are registered --- .../java/com/commafeed/CommaFeedApplication.java | 16 +++++++++++----- src/main/java/com/commafeed/CommaFeedModule.java | 15 +++++++++++---- .../commafeed/backend/task/ScheduledTask.java | 3 ++- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/commafeed/CommaFeedApplication.java b/src/main/java/com/commafeed/CommaFeedApplication.java index d5c9410d..5369dd73 100644 --- a/src/main/java/com/commafeed/CommaFeedApplication.java +++ b/src/main/java/com/commafeed/CommaFeedApplication.java @@ -13,6 +13,7 @@ import io.dropwizard.setup.Environment; import java.io.IOException; import java.util.Date; import java.util.EnumSet; +import java.util.Set; import java.util.concurrent.ScheduledExecutorService; import javax.servlet.DispatcherType; @@ -42,8 +43,7 @@ import com.commafeed.backend.model.UserRole; import com.commafeed.backend.model.UserSettings; import com.commafeed.backend.service.StartupService; import com.commafeed.backend.service.UserService; -import com.commafeed.backend.task.OldStatusesCleanupTask; -import com.commafeed.backend.task.OrphansCleanupTask; +import com.commafeed.backend.task.ScheduledTask; import com.commafeed.frontend.auth.SecurityCheckFactoryProvider; import com.commafeed.frontend.resource.AdminREST; import com.commafeed.frontend.resource.CategoryREST; @@ -59,6 +59,8 @@ import com.commafeed.frontend.servlet.NextUnreadServlet; import com.commafeed.frontend.session.SessionHelperFactoryProvider; import com.google.inject.Guice; import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.TypeLiteral; import com.wordnik.swagger.config.ConfigFactory; import com.wordnik.swagger.config.ScannerFactory; import com.wordnik.swagger.config.SwaggerConfig; @@ -142,9 +144,13 @@ public class CommaFeedApplication extends Application { environment.servlets().addServlet("analytics.js", injector.getInstance(AnalyticsServlet.class)).addMapping("/analytics.js"); // Scheduled tasks - ScheduledExecutorService executor = environment.lifecycle().scheduledExecutorService("task-scheduler", true).build(); - injector.getInstance(OldStatusesCleanupTask.class).register(executor); - injector.getInstance(OrphansCleanupTask.class).register(executor); + Set tasks = injector.getInstance(Key.get(new TypeLiteral>() { + })); + ScheduledExecutorService executor = environment.lifecycle().scheduledExecutorService("task-scheduler", true).threads(tasks.size()) + .build(); + for (ScheduledTask task : tasks) { + task.register(executor); + } // database init/changelogs environment.lifecycle().manage(injector.getInstance(StartupService.class)); diff --git a/src/main/java/com/commafeed/CommaFeedModule.java b/src/main/java/com/commafeed/CommaFeedModule.java index d290f68f..5edd9feb 100644 --- a/src/main/java/com/commafeed/CommaFeedModule.java +++ b/src/main/java/com/commafeed/CommaFeedModule.java @@ -15,6 +15,9 @@ import com.commafeed.backend.favicon.AbstractFaviconFetcher; import com.commafeed.backend.favicon.DefaultFaviconFetcher; import com.commafeed.backend.favicon.FacebookFaviconFetcher; import com.commafeed.backend.favicon.YoutubeFaviconFetcher; +import com.commafeed.backend.task.OldStatusesCleanupTask; +import com.commafeed.backend.task.OrphansCleanupTask; +import com.commafeed.backend.task.ScheduledTask; import com.google.inject.AbstractModule; import com.google.inject.Provides; import com.google.inject.multibindings.Multibinder; @@ -39,9 +42,13 @@ public class CommaFeedModule extends AbstractModule { log.info("using cache {}", cacheService.getClass()); bind(CacheService.class).toInstance(cacheService); - Multibinder multibinder = Multibinder.newSetBinder(binder(), AbstractFaviconFetcher.class); - multibinder.addBinding().to(YoutubeFaviconFetcher.class); - multibinder.addBinding().to(FacebookFaviconFetcher.class); - multibinder.addBinding().to(DefaultFaviconFetcher.class); + Multibinder faviconMultibinder = Multibinder.newSetBinder(binder(), AbstractFaviconFetcher.class); + faviconMultibinder.addBinding().to(YoutubeFaviconFetcher.class); + faviconMultibinder.addBinding().to(FacebookFaviconFetcher.class); + faviconMultibinder.addBinding().to(DefaultFaviconFetcher.class); + + Multibinder taskMultibinder = Multibinder.newSetBinder(binder(), ScheduledTask.class); + taskMultibinder.addBinding().to(OldStatusesCleanupTask.class); + taskMultibinder.addBinding().to(OrphansCleanupTask.class); } } diff --git a/src/main/java/com/commafeed/backend/task/ScheduledTask.java b/src/main/java/com/commafeed/backend/task/ScheduledTask.java index bed7c5b5..7976194b 100644 --- a/src/main/java/com/commafeed/backend/task/ScheduledTask.java +++ b/src/main/java/com/commafeed/backend/task/ScheduledTask.java @@ -26,7 +26,8 @@ public abstract class ScheduledTask { } } }; + log.info("registering task {} for execution every {} {}, starting in {} {}", getClass().getSimpleName(), getPeriod(), + getTimeUnit(), getInitialDelay(), getTimeUnit()); executor.scheduleWithFixedDelay(runnable, getInitialDelay(), getPeriod(), getTimeUnit()); } - } From 60b8af38609ee37d99069ed7cd61028e55d8f001 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 24 Nov 2014 12:52:57 +0100 Subject: [PATCH 062/241] typos --- src/main/java/com/commafeed/CommaFeedApplication.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/commafeed/CommaFeedApplication.java b/src/main/java/com/commafeed/CommaFeedApplication.java index 5369dd73..21957d70 100644 --- a/src/main/java/com/commafeed/CommaFeedApplication.java +++ b/src/main/java/com/commafeed/CommaFeedApplication.java @@ -118,9 +118,9 @@ public class CommaFeedApplication extends Application { // session management environment.servlets().setSessionHandler(new SessionHandler(config.getSessionManagerFactory().build())); - // support for "@SecurityCheck User user" intection + // support for "@SecurityCheck User user" injection environment.jersey().register(new SecurityCheckFactoryProvider.Binder(injector.getInstance(UserService.class))); - // support for "@COntext SessionHelper sessionHelper" intection + // support for "@Context SessionHelper sessionHelper" injection environment.jersey().register(new SessionHelperFactoryProvider.Binder()); // REST resources From a705cbe6c2bb55e46fb6c742c1f65a2b66bf84e2 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 24 Nov 2014 12:56:55 +0100 Subject: [PATCH 063/241] instantiate filtering service only once --- .../backend/service/FeedEntryFilteringService.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/com/commafeed/backend/service/FeedEntryFilteringService.java b/src/main/java/com/commafeed/backend/service/FeedEntryFilteringService.java index 1b866acb..038d5393 100644 --- a/src/main/java/com/commafeed/backend/service/FeedEntryFilteringService.java +++ b/src/main/java/com/commafeed/backend/service/FeedEntryFilteringService.java @@ -8,6 +8,11 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import javax.inject.Inject; +import javax.inject.Singleton; + +import lombok.RequiredArgsConstructor; + import org.apache.commons.jexl2.JexlContext; import org.apache.commons.jexl2.JexlEngine; import org.apache.commons.jexl2.JexlException; @@ -24,6 +29,8 @@ import org.jsoup.Jsoup; import com.commafeed.backend.model.FeedEntry; +@RequiredArgsConstructor(onConstructor = @__({ @Inject })) +@Singleton public class FeedEntryFilteringService { private static final JexlEngine ENGINE = initEngine(); From e65dd49d69b6a313b69f5b8f8a36fd38ccbe70bc Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 24 Nov 2014 13:13:43 +0100 Subject: [PATCH 064/241] use released version of device.js --- bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bower.json b/bower.json index b08b6435..5a4e9f95 100644 --- a/bower.json +++ b/bower.json @@ -25,7 +25,7 @@ "ng-grid": "2.0.6", "mousetrap": "1.4.6", "momentjs": "2.8.3", - "device.js": "matthewhudson/device.js#2ae5c775e35ccc837589e5af34e292c54936778c", + "devicejs": "0.1.16", "readabilicons": "arc90/readability-readabilicons#34c55561c5b8ec6e90714b50237c06b13cb9d59c", "zocial": "samcollins/css-social-buttons#1f59ecacde475e563fb6771667597493ec4eecb6", "swagger-ui": "2.0.24" From 84d7a501d4fb1d36d7185cea17490e174f469f90 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 24 Nov 2014 14:17:38 +0100 Subject: [PATCH 065/241] directory name change since it's devicejs on bower --- src/main/app/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/app/index.html b/src/main/app/index.html index 63021056..2274a13c 100644 --- a/src/main/app/index.html +++ b/src/main/app/index.html @@ -63,7 +63,7 @@ - + From 9020d95b6219eee6914cdf0c662ee49fa9670970 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 24 Nov 2014 14:55:20 +0100 Subject: [PATCH 066/241] better character encoding detection --- pom.xml | 6 +++--- .../com/commafeed/backend/feed/FeedUtils.java | 21 ++++++++++--------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/pom.xml b/pom.xml index 02845c26..eb6cf3e8 100644 --- a/pom.xml +++ b/pom.xml @@ -304,9 +304,9 @@ 1.8.1 - com.googlecode.juniversalchardet - juniversalchardet - 1.0.3 + com.ibm.icu + icu4j + 54.1.1 net.sourceforge.cssparser diff --git a/src/main/java/com/commafeed/backend/feed/FeedUtils.java b/src/main/java/com/commafeed/backend/feed/FeedUtils.java index fca78591..4ce026bc 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedUtils.java +++ b/src/main/java/com/commafeed/backend/feed/FeedUtils.java @@ -25,7 +25,6 @@ import org.jsoup.nodes.Entities.EscapeMode; import org.jsoup.safety.Cleaner; import org.jsoup.safety.Whitelist; import org.jsoup.select.Elements; -import org.mozilla.universalchardet.UniversalDetector; import org.w3c.css.sac.InputSource; import org.w3c.dom.css.CSSStyleDeclaration; @@ -34,6 +33,8 @@ import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedSubscription; import com.commafeed.frontend.model.Entry; import com.google.common.collect.Lists; +import com.ibm.icu.text.CharsetDetector; +import com.ibm.icu.text.CharsetMatch; import com.steadystate.css.parser.CSSOMParser; import edu.uci.ics.crawler4j.url.URLCanonicalizer; @@ -114,15 +115,15 @@ public class FeedUtils { * Detect encoding by analyzing characters in the array */ public static String detectEncoding(byte[] bytes) { - String DEFAULT_ENCODING = "UTF-8"; - UniversalDetector detector = new UniversalDetector(null); - detector.handleData(bytes, 0, bytes.length); - detector.dataEnd(); - String encoding = detector.getDetectedCharset(); - detector.reset(); - if (encoding == null) { - encoding = DEFAULT_ENCODING; - } else if (encoding.equalsIgnoreCase("ISO-8859-1")) { + String encoding = "UTF-8"; + + CharsetDetector detector = new CharsetDetector(); + detector.setText(bytes); + CharsetMatch match = detector.detect(); + if (match != null) { + encoding = match.getName(); + } + if (encoding.equalsIgnoreCase("ISO-8859-1")) { encoding = "windows-1252"; } return encoding; From 435fcb966997ee65b3a8727106e1512402f7d681 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 24 Nov 2014 16:27:32 +0100 Subject: [PATCH 067/241] unused method --- .../java/com/commafeed/backend/dao/FeedEntryDAO.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/main/java/com/commafeed/backend/dao/FeedEntryDAO.java b/src/main/java/com/commafeed/backend/dao/FeedEntryDAO.java index 65f9997f..d5016419 100644 --- a/src/main/java/com/commafeed/backend/dao/FeedEntryDAO.java +++ b/src/main/java/com/commafeed/backend/dao/FeedEntryDAO.java @@ -11,9 +11,7 @@ import org.hibernate.SessionFactory; import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.FeedEntry; -import com.commafeed.backend.model.QFeed; import com.commafeed.backend.model.QFeedEntry; -import com.commafeed.backend.model.QFeedSubscription; import com.google.common.collect.Iterables; @Singleton @@ -32,13 +30,6 @@ public class FeedEntryDAO extends GenericDAO { return Iterables.getFirst(list, null); } - public List findWithoutSubscriptions(int max) { - QFeed feed = QFeed.feed; - QFeedSubscription sub = QFeedSubscription.feedSubscription; - return newQuery().from(entry).join(entry.feed, feed).leftJoin(feed.subscriptions, sub).where(sub.id.isNull()).limit(max) - .list(entry); - } - public int delete(Date olderThan, int max) { List list = newQuery().from(entry).where(entry.inserted.lt(olderThan)).limit(max).list(entry); int deleted = list.size(); From ed81fc576a94ae43d7ecce3062696f5fd16dfe99 Mon Sep 17 00:00:00 2001 From: Athou Date: Tue, 25 Nov 2014 10:30:14 +0100 Subject: [PATCH 068/241] fix tagging issues --- src/main/app/js/directives.js | 2 +- src/main/app/js/services.js | 1 + src/main/app/templates/_tags.html | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/app/js/directives.js b/src/main/app/js/directives.js index 463877c5..eef070be 100644 --- a/src/main/app/js/directives.js +++ b/src/main/app/js/directives.js @@ -72,7 +72,7 @@ module.directive('tags', function() { tags : [] }; if (newValue) { - data.tags = newValue.split(','); + data.tags = newValue; } EntryService.tag(data); } diff --git a/src/main/app/js/services.js b/src/main/app/js/services.js index c4807b4d..57fc7e34 100644 --- a/src/main/app/js/services.js +++ b/src/main/app/js/services.js @@ -298,6 +298,7 @@ module.factory('EntryService', ['$resource', '$http', function($resource, $http) $http.get('rest/entry/tags').success(function(data) { res.tags = []; res.tags.push.apply(res.tags, data); + res.tags.sort(); }); }; var oldTag = res.tag; diff --git a/src/main/app/templates/_tags.html b/src/main/app/templates/_tags.html index dd1d20e4..31923388 100644 --- a/src/main/app/templates/_tags.html +++ b/src/main/app/templates/_tags.html @@ -7,6 +7,6 @@ {{tag}} - + \ No newline at end of file From 77c3ec0bbe91cc09fad1e48618cba3ff5d52b53a Mon Sep 17 00:00:00 2001 From: Athou Date: Wed, 26 Nov 2014 14:19:08 +0100 Subject: [PATCH 069/241] support for single quotes (#681) --- .../java/com/commafeed/backend/feed/FeedUtils.java | 4 ++-- .../com/commafeed/backend/feed/FeedUtilsTest.java | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/commafeed/backend/feed/FeedUtils.java b/src/main/java/com/commafeed/backend/feed/FeedUtils.java index 4ce026bc..88e0daad 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedUtils.java +++ b/src/main/java/com/commafeed/backend/feed/FeedUtils.java @@ -185,12 +185,12 @@ public class FeedUtils { } String pi = new String(ArrayUtils.subarray(bytes, 0, index + 1)); - index = StringUtils.indexOf(pi, "encoding=\""); + index = StringUtils.indexOf(pi, "encoding="); if (index == -1) { return null; } String encoding = pi.substring(index + 10, pi.length()); - encoding = encoding.substring(0, encoding.indexOf('"')); + encoding = encoding.substring(0, Math.max(encoding.indexOf(' ') - 1, 0)); return encoding; } diff --git a/src/test/java/com/commafeed/backend/feed/FeedUtilsTest.java b/src/test/java/com/commafeed/backend/feed/FeedUtilsTest.java index 488b90c3..f333ca4e 100644 --- a/src/test/java/com/commafeed/backend/feed/FeedUtilsTest.java +++ b/src/test/java/com/commafeed/backend/feed/FeedUtilsTest.java @@ -3,8 +3,6 @@ package com.commafeed.backend.feed; import org.junit.Assert; import org.junit.Test; -import com.commafeed.backend.feed.FeedUtils; - public class FeedUtilsTest { @Test @@ -55,4 +53,13 @@ public class FeedUtilsTest { FeedUtils.toAbsoluteUrl("elisp_all_about_lines.html", "blog.xml", "http://ergoemacs.org/emacs/blog.xml")); } + + @Test + public void testExtractDeclaredEncoding() { + Assert.assertNull(FeedUtils.extractDeclaredEncoding("".getBytes())); + Assert.assertNull(FeedUtils.extractDeclaredEncoding("".getBytes())); + Assert.assertEquals("UTF-8", FeedUtils.extractDeclaredEncoding("".getBytes())); + Assert.assertEquals("UTF-8", FeedUtils.extractDeclaredEncoding("".getBytes())); + + } } From cca300e4190f55364387256e68c53b9e0ad9a288 Mon Sep 17 00:00:00 2001 From: Athou Date: Wed, 26 Nov 2014 15:25:38 +0100 Subject: [PATCH 070/241] simpler support for single quotes (#681) --- src/main/java/com/commafeed/backend/feed/FeedUtils.java | 6 +++--- src/test/java/com/commafeed/backend/feed/FeedUtilsTest.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/commafeed/backend/feed/FeedUtils.java b/src/main/java/com/commafeed/backend/feed/FeedUtils.java index 88e0daad..bb863a80 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedUtils.java +++ b/src/main/java/com/commafeed/backend/feed/FeedUtils.java @@ -184,13 +184,13 @@ public class FeedUtils { return null; } - String pi = new String(ArrayUtils.subarray(bytes, 0, index + 1)); - index = StringUtils.indexOf(pi, "encoding="); + String pi = new String(ArrayUtils.subarray(bytes, 0, index + 1)).replace('\'', '"'); + index = StringUtils.indexOf(pi, "encoding=\""); if (index == -1) { return null; } String encoding = pi.substring(index + 10, pi.length()); - encoding = encoding.substring(0, Math.max(encoding.indexOf(' ') - 1, 0)); + encoding = encoding.substring(0, encoding.indexOf('"')); return encoding; } diff --git a/src/test/java/com/commafeed/backend/feed/FeedUtilsTest.java b/src/test/java/com/commafeed/backend/feed/FeedUtilsTest.java index f333ca4e..7115f330 100644 --- a/src/test/java/com/commafeed/backend/feed/FeedUtilsTest.java +++ b/src/test/java/com/commafeed/backend/feed/FeedUtilsTest.java @@ -60,6 +60,6 @@ public class FeedUtilsTest { Assert.assertNull(FeedUtils.extractDeclaredEncoding("".getBytes())); Assert.assertEquals("UTF-8", FeedUtils.extractDeclaredEncoding("".getBytes())); Assert.assertEquals("UTF-8", FeedUtils.extractDeclaredEncoding("".getBytes())); - + Assert.assertEquals("UTF-8", FeedUtils.extractDeclaredEncoding("".getBytes())); } } From 8b68fb578f7813f1c71d9f4fccc3177fdf940baa Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 27 Nov 2014 22:04:13 +0100 Subject: [PATCH 071/241] swagger upgrade (no longer includes logback.xml) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index eb6cf3e8..40e9edee 100644 --- a/pom.xml +++ b/pom.xml @@ -230,7 +230,7 @@ com.wordnik swagger-jaxrs_2.10 - 1.3.10 + 1.3.11 javax.ws.rs From d06359cb81015e58171d77549f4bc48aa6b50d9a Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 1 Dec 2014 16:02:41 +0100 Subject: [PATCH 072/241] remove deb package creation (fix #684) --- pom.xml | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/pom.xml b/pom.xml index 40e9edee..4c8379ff 100644 --- a/pom.xml +++ b/pom.xml @@ -139,29 +139,6 @@ - - com.jamierf.dropwizard - dropwizard-debpkg-maven-plugin - 0.7 - - ${basedir}/config.yml.example - - openjdk-7-jdk - true - - - commafeed - - - - - package - - dwpackage - - - - From f93796d036015ece412f5f7a68bdff0fb24341e8 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 1 Dec 2014 16:19:30 +0100 Subject: [PATCH 073/241] fix for "handshake alert: unrecognized_name" (fix #685) --- src/main/java/com/commafeed/backend/HttpGetter.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/commafeed/backend/HttpGetter.java b/src/main/java/com/commafeed/backend/HttpGetter.java index 78921ae8..330e6d93 100644 --- a/src/main/java/com/commafeed/backend/HttpGetter.java +++ b/src/main/java/com/commafeed/backend/HttpGetter.java @@ -58,6 +58,9 @@ public class HttpGetter { private static SSLContext SSL_CONTEXT = null; static { + // fix for "handshake alert: unrecognized_name" + System.setProperty("jsse.enableSNIExtension", "false"); + try { SSL_CONTEXT = SSLContext.getInstance("TLS"); SSL_CONTEXT.init(new KeyManager[0], new TrustManager[] { new DefaultTrustManager() }, new SecureRandom()); From e81dda0fa80b281e769eb8d7d922359cd77bb596 Mon Sep 17 00:00:00 2001 From: Ebrahim Byagowi Date: Wed, 3 Dec 2014 16:49:26 +0000 Subject: [PATCH 074/241] Support Android 5.0 bits http://updates.html5rocks.com/2014/11/Support-for-theme-color-in-Chrome-39-for-Android --- src/main/app/app-icon-192.png | Bin 0 -> 6069 bytes src/main/app/index.html | 2 ++ 2 files changed, 2 insertions(+) create mode 100644 src/main/app/app-icon-192.png diff --git a/src/main/app/app-icon-192.png b/src/main/app/app-icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..cae01546dd30eb6eb4e5d8523d7b43ebbd1bc45f GIT binary patch literal 6069 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 q4UDo%K1Lh`H# - commons-lang commons-lang + commons-lang + + + com.fasterxml.jackson.module + jackson-module-scala_2.10 + + + org.scala-lang + scalap From 5a493cd55d8bdf4ab7cf71d0aeb518e4a04425ae Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 12 Dec 2014 11:25:31 +0100 Subject: [PATCH 100/241] rewrite using lambda --- .../java/com/commafeed/frontend/resource/AdminREST.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/java/com/commafeed/frontend/resource/AdminREST.java b/src/main/java/com/commafeed/frontend/resource/AdminREST.java index 1f47168b..e3a81cf9 100644 --- a/src/main/java/com/commafeed/frontend/resource/AdminREST.java +++ b/src/main/java/com/commafeed/frontend/resource/AdminREST.java @@ -123,11 +123,7 @@ public class AdminREST { userModel.setName(u.getName()); userModel.setEmail(u.getEmail()); userModel.setEnabled(!u.isDisabled()); - for (UserRole role : userRoleDAO.findAll(u)) { - if (role.getRole() == Role.ADMIN) { - userModel.setAdmin(true); - } - } + userModel.setAdmin(userRoleDAO.findAll(u).stream().anyMatch(r -> r.getRole() == Role.ADMIN)); return Response.ok(userModel).build(); } From cbf9f65fb42b5e6cdaab1ea0cc44cf56049bf4d5 Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 12 Dec 2014 11:53:20 +0100 Subject: [PATCH 101/241] use released version of zocial --- bower.json | 2 +- gulpfile.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 5a4e9f95..bfc83c31 100644 --- a/bower.json +++ b/bower.json @@ -27,7 +27,7 @@ "momentjs": "2.8.3", "devicejs": "0.1.16", "readabilicons": "arc90/readability-readabilicons#34c55561c5b8ec6e90714b50237c06b13cb9d59c", - "zocial": "samcollins/css-social-buttons#1f59ecacde475e563fb6771667597493ec4eecb6", + "zocial-less": "1.0.0", "swagger-ui": "2.0.24" }, "resolutions": { diff --git a/gulpfile.js b/gulpfile.js index 9eca9a04..ee3bcbf5 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -40,7 +40,7 @@ gulp.task('sass', function() { gulp.task('fonts', ['bower'], function() { var font_awesome = SRC_DIR + 'lib/font-awesome/font/fontawesome-webfont.*'; - var zocial = SRC_DIR + 'lib/zocial/css/zocial-regular-*'; + var zocial = SRC_DIR + 'lib/zocial-less/css/zocial-regular-*'; var readabilicons = SRC_DIR + 'lib/readabilicons/webfont/fonts/readabilicons-*'; return gulp.src([font_awesome, zocial, readabilicons]).pipe(gulp.dest(BUILD_DIR + 'font')); }); From 090462022fecbad44bc3191cce488d3c81bb50eb Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 12 Dec 2014 11:56:07 +0100 Subject: [PATCH 102/241] call bower prune before calling bower install --- gulpfile.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index ee3bcbf5..63e58680 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -15,7 +15,13 @@ var SRC_DIR = 'src/main/app/'; var TEMP_DIR = 'target/gulp/' var BUILD_DIR = 'target/classes/assets/'; -gulp.task('bower', function() { +gulp.task('bower_prune', function() { + return bower({ + cmd : 'prune' + }); +}); + +gulp.task('bower', ['bower_prune'], function() { return bower(); }); @@ -101,7 +107,7 @@ gulp.task('serve', function() { connect.server({ root : BUILD_DIR, port : 8082, - livereload: true, + livereload : true, middleware : function() { var rest = '^/rest/(.*)$ http://localhost:8083/rest/$1 [P]'; var next = '^/next(.*)$ http://localhost:8083/next$1 [P]'; From dad4c6b8663ee9d94ee96074c5368ada170beb04 Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 12 Dec 2014 11:58:51 +0100 Subject: [PATCH 103/241] build deps upgrade --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 037779af..addc4492 100644 --- a/package.json +++ b/package.json @@ -8,12 +8,12 @@ "gulp-rev": "2.0.1", "gulp-rev-replace": "0.3.1", "gulp-minify-css": "0.3.11", - "gulp-uglify": "1.0.1", + "gulp-uglify": "1.0.2", "gulp-filter": "1.0.2", "gulp-bower": "0.0.7", "gulp-connect": "2.2.0", "connect-modrewrite": "0.7.9", - "gulp-sass": "1.1.0", + "gulp-sass": "1.2.4", "gulp-useref": "1.0.2", "gulp-angular-templatecache": "1.4.2" } From 906801e13c1fde60e13b8b793928cb6e316ee954 Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 12 Dec 2014 12:03:07 +0100 Subject: [PATCH 104/241] runtime deps upgrade --- bower.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/bower.json b/bower.json index bfc83c31..fcbe71d9 100644 --- a/bower.json +++ b/bower.json @@ -8,30 +8,30 @@ "lodash": "2.4.1", "bootstrap": "3.3.1", "font-awesome": "3.2.1", - "angular": "1.3.2", - "angular-resource": "1.3.2", - "angular-route": "1.3.2", - "angular-sanitize": "1.3.2", - "angular-touch": "1.3.2", - "angular-animate": "1.3.2", - "angular-ui-router": "0.2.12", + "angular": "1.3.6", + "angular-resource": "1.3.6", + "angular-route": "1.3.6", + "angular-sanitize": "1.3.6", + "angular-touch": "1.3.6", + "angular-animate": "1.3.6", + "angular-ui-router": "0.2.13", "angular-ui-utils": "0.1.0", "angular-ui-select2": "0.0.5", "angular-bootstrap": "0.2.0", "angular-loading-bar": "0.6.0", - "angular-translate": "2.4.2", - "angular-translate-loader-static-files": "2.4.2", + "angular-translate": "2.5.2", + "angular-translate-loader-static-files": "2.5.2", "ngInfiniteScroll": "1.0.0", "ng-grid": "2.0.6", "mousetrap": "1.4.6", - "momentjs": "2.8.3", + "momentjs": "2.8.4", "devicejs": "0.1.16", "readabilicons": "arc90/readability-readabilicons#34c55561c5b8ec6e90714b50237c06b13cb9d59c", "zocial-less": "1.0.0", "swagger-ui": "2.0.24" }, "resolutions": { - "angular": "1.3.2", - "angular-translate": "2.4.2" + "angular": "1.3.6", + "angular-translate": "2.5.2" } } From 34c5c0b1f7768930227a45780647f3e82e61c96d Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 12 Dec 2014 15:17:36 +0100 Subject: [PATCH 105/241] fix #691 (reopens #685) --- src/main/java/com/commafeed/backend/HttpGetter.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/com/commafeed/backend/HttpGetter.java b/src/main/java/com/commafeed/backend/HttpGetter.java index 330e6d93..78921ae8 100644 --- a/src/main/java/com/commafeed/backend/HttpGetter.java +++ b/src/main/java/com/commafeed/backend/HttpGetter.java @@ -58,9 +58,6 @@ public class HttpGetter { private static SSLContext SSL_CONTEXT = null; static { - // fix for "handshake alert: unrecognized_name" - System.setProperty("jsse.enableSNIExtension", "false"); - try { SSL_CONTEXT = SSLContext.getInstance("TLS"); SSL_CONTEXT.init(new KeyManager[0], new TrustManager[] { new DefaultTrustManager() }, new SecureRandom()); From caff34cc3b6eb89e78071f9133a00fc9914a8b0a Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 12 Dec 2014 15:48:40 +0100 Subject: [PATCH 106/241] small perf boost --- src/main/java/com/commafeed/backend/feed/FeedUtils.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/commafeed/backend/feed/FeedUtils.java b/src/main/java/com/commafeed/backend/feed/FeedUtils.java index 51ecec79..6347051b 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedUtils.java +++ b/src/main/java/com/commafeed/backend/feed/FeedUtils.java @@ -10,6 +10,7 @@ import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -133,8 +134,8 @@ public class FeedUtils { public static String replaceHtmlEntitiesWithNumericEntities(String source) { String result = source; - for (String entity : HtmlEntities.NUMERIC_MAPPING.keySet()) { - result = StringUtils.replace(result, entity, HtmlEntities.NUMERIC_MAPPING.get(entity)); + for (Map.Entry entry : HtmlEntities.NUMERIC_MAPPING.entrySet()) { + result = StringUtils.replace(result, entry.getKey(), entry.getValue()); } return result; } From 9d044195aafe524382db06c2f4187d6bdf01a08b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ak=C4=B1n=20Ayturan?= Date: Sat, 13 Dec 2014 12:08:26 +0200 Subject: [PATCH 107/241] Update tr.js --- src/main/app/i18n/tr.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/app/i18n/tr.js b/src/main/app/i18n/tr.js index b03abb2a..c9e74685 100644 --- a/src/main/app/i18n/tr.js +++ b/src/main/app/i18n/tr.js @@ -8,7 +8,7 @@ "link" : "BaÄŸlantı", "bookmark" : "Yer imi", "close" : "Kapat", - "tags" : "Tags " + "tags" : "Etiketler " }, "tree" : { "subscribe" : "Abone ol", @@ -24,7 +24,7 @@ }, "import" : { "google_reader_prefix" : "Aboneliklerinizi ", - "google_reader_suffix" : " hesabınızdan aktarmama izin verin.", + "google_reader_suffix" : "Hesabınızdan aktarmama izin verin.", "google_download" : "Veya, subscriptions.xml dosyanızı yükleyin.", "google_download_link" : "Buradan indirebilirsiniz.", "xml_file" : "OPML dosyası" @@ -39,12 +39,12 @@ "previous_entry" : "Önceki ileti", "next_entry" : "Sonraki ileti", "refresh" : "Yenile", - "refresh_all" : "Force refresh all my feeds ", + "refresh_all" : "Tüm yayınları yenilemek için zorla", "sort_by_asc_desc" : "Tarihe göre sırala artan/azalan", "titles_only" : "Sadece baÅŸlıklar", "expanded_view" : "GeniÅŸletilmiÅŸ görünüm", "mark_all_as_read" : "Tümünü okundu iÅŸaretle", - "mark_all_older_12_hours" : "Items older than 12 hours ", + "mark_all_older_12_hours" : "12 saatten daha eski öğeler ", "mark_all_older_day" : "Bir günden eski yazılar", "mark_all_older_week" : "Bir haftadan eski yazılar", "mark_all_older_two_weeks" : "İki haftadan eski yazılar", @@ -100,10 +100,10 @@ "feed_url" : "Yayın URL'si", "generate_api_key_first" : "Öncelikle profilinizden bir API anahtarı oluÅŸturun.", "unsubscribe" : "AboneliÄŸi iptal et", - "unsubscribe_confirmation" : "Are you sure you want to unsubscribe from this feed? ", - "delete_category_confirmation" : "Are you sure you want to delete this category? ", + "unsubscribe_confirmation" : "Bu yayından çıkmak istediÄŸinizden emin misiniz? ", + "delete_category_confirmation" : "Bu kategoriyi silmek istediÄŸinizden emin misiniz? ", "category_details" : "Kategori detayları", - "tag_details" : "Tag details ", + "tag_details" : "Etiket detayları ", "parent_category" : "Üst kategori" }, "profile" : { @@ -119,7 +119,7 @@ "generate_new_api_key_info" : "Åžifre deÄŸiÅŸtirmek API anahtarının da deÄŸiÅŸtirilmesine neden olcak.", "opml_export" : "OPML dışa aktar", "delete_account" : "Hesabı sil", - "delete_account_confirmation" : "Delete your acount? There's no turning back! " + "delete_account_confirmation" : "Hesabı silmek istediÄŸinize emin misiniz? Bu iÅŸlemde geri dönüş yoktur! " }, "about" : { "rest_api" : { @@ -128,7 +128,7 @@ "link_to_documentation" : "Dökümantasyon için tıklayın." }, "keyboard_shortcuts" : "Klavye kısayolları", - "version" : "CommaFeed version ", + "version" : "CommaFeed versiyon ", "line1_prefix" : "CommaFeed bir açık kaynak projedir. Kaynak dosyaları ", "line1_suffix" : " adresinde yayınlanır.", "line2_prefix" : "Lütfen, bir hata ile karşılaşırsanız bunu ", @@ -177,4 +177,4 @@ "feed_search" : "abonelik ismini yazarak aboneliÄŸe git" } } -} \ No newline at end of file +} From 94e58a449c6f64c37db83e423e0d3409655447eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ak=C4=B1n=20Ayturan?= Date: Sat, 13 Dec 2014 12:17:37 +0200 Subject: [PATCH 108/241] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9bdf5e73..bea55f9a 100644 --- a/README.md +++ b/README.md @@ -75,11 +75,11 @@ The language has to be referenced in the `src/main/app/js/i18n.js` file to be pi ## Themes -To create a theme, create a new file `src/main/webapp/sass/themes/_.scss`. Your styles should be wrapped in a `#theme-` element and use the [SCSS format](http://sass-lang.com/) which is a superset of CSS. +To create a theme, create a new file `src/main/app/sass/themes/_.scss`. Your styles should be wrapped in a `#theme-` element and use the [SCSS format](http://sass-lang.com/) which is a superset of CSS. -Don't forget to reference your theme in `src/main/webapp/sass/app.scss` and in `src/main/webapp/js/controllers.js` (look for `$scope.themes`). +Don't forget to reference your theme in `src/main/app/sass/app.scss` and in `src/main/app/js/controllers.js` (look for `$scope.themes`). -See [_test.scss](https://github.com/Athou/commafeed/blob/master/src/main/webapp/sass/themes/_test.scss) for an example. +See [_test.scss](https://github.com/Athou/commafeed/blob/master/src/main/app/sass/themes/_test.scss) for an example. ## Local development From 09cfa21091630be40e0f2bd22bb5434e231ade7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ak=C4=B1n=20Ayturan?= Date: Sun, 14 Dec 2014 13:07:33 +0200 Subject: [PATCH 109/241] Update tr.js a little change --- src/main/app/i18n/tr.js | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/main/app/i18n/tr.js b/src/main/app/i18n/tr.js index c9e74685..89680295 100644 --- a/src/main/app/i18n/tr.js +++ b/src/main/app/i18n/tr.js @@ -56,14 +56,14 @@ "donate" : "Bağış" }, "view" : { - "entry_source" : "from ", - "entry_author" : "by ", + "entry_source" : "site: ", + "entry_author" : "yazar: ", "error_while_loading_feed" : "Bu aboneliÄŸi çekerken hata oluÅŸtu.", "keep_unread" : "Okunmadı olarak sakla", - "no_unread_items" : "okunmamış ileti yok.", + "no_unread_items" : "Okunmamış ileti yok.", "mark_up_to_here" : "Mark as read up to here ", "search_for" : "searching for: ", - "no_search_results" : "No match found for the requested keywords " + "no_search_results" : "İstenen anahtar kelimeler için eÅŸleÅŸme bulunamadı" }, "feedsearch" : { "hint" : "Bir abonelik yazın...", @@ -80,8 +80,8 @@ "scroll_marks" : "GeniÅŸletilmiÅŸ görünümde götüntülenen iletileri okunmuÅŸ iÅŸaretle" }, "appearance" : "Görünüm", - "scroll_speed" : "Scrolling speed when navigating between entries (in milliseconds) ", - "scroll_speed_help" : "set to 0 to disable ", + "scroll_speed" : "İçerikler arasında gezinirken kaydırma hızı (milisaniye cinsinden)", + "scroll_speed_help" : "ayarı kapatmak için 0 yazınız", "theme" : "Tema", "submit_your_theme" : "Tema gönder", "custom_css" : "KiÅŸiselleÅŸtirilmiÅŸ CSS" @@ -116,7 +116,7 @@ "api_key" : "API anahtarı", "api_key_not_generated" : "Henüz oluÅŸturulmadı", "generate_new_api_key" : "Yeni bir API anahtarı oluÅŸtur", - "generate_new_api_key_info" : "Åžifre deÄŸiÅŸtirmek API anahtarının da deÄŸiÅŸtirilmesine neden olcak.", + "generate_new_api_key_info" : "Åžifreyi deÄŸiÅŸtirmek API anahtarının da deÄŸiÅŸtirilmesine neden olcak.", "opml_export" : "OPML dışa aktar", "delete_account" : "Hesabı sil", "delete_account_confirmation" : "Hesabı silmek istediÄŸinize emin misiniz? Bu iÅŸlemde geri dönüş yoktur! " @@ -130,22 +130,22 @@ "keyboard_shortcuts" : "Klavye kısayolları", "version" : "CommaFeed versiyon ", "line1_prefix" : "CommaFeed bir açık kaynak projedir. Kaynak dosyaları ", - "line1_suffix" : " adresinde yayınlanır.", + "line1_suffix" : "adresinde yayınlanır.", "line2_prefix" : "Lütfen, bir hata ile karşılaşırsanız bunu ", - "line2_suffix" : " projesinde hatalar sayfasından rapor edin.", + "line2_suffix" : "projesinde hatalar sayfasından rapor edin.", "line3" : "EÄŸer bu projeyi beÄŸendiyseniz, lütfen bağış yaparak geliÅŸtiriciye bu sayfayı ayakta tutmasında yardımcı olun.", "line4" : "Bitcoin'i tercih edenler için adres ", "goodies" : { - "value" : "Extralar", - "android_app" : "Android app ", + "value" : "Ekstralar", + "android_app" : "Android eklentisi", "subscribe_url" : "Abonelik URL'si", "chrome_extension" : "Chrome eklentisi", "firefox_extension" : "Firefox eklentisi", "opera_extension" : "Opera eklentisi", - "subscribe_bookmarklet" : "Bookmarklet'a abonelik ekle (tıklayın)", - "subscribe_bookmarklet_asc" : "Oldest first ", - "subscribe_bookmarklet_desc" : "Newest first ", - "next_unread_bookmarklet" : "Bookmarklet'daki en son okunmamış ileti (Sık kullanılan çubuÄŸuna sürükleyin)" + "subscribe_bookmarklet" : "Yer imilerine abonelik ekle (tıklayın)", + "subscribe_bookmarklet_asc" : "Eskiler önce", + "subscribe_bookmarklet_desc" : "Yeniler önce ", + "next_unread_bookmarklet" : "Yer imilerindeki en son okunmamış ileti (Sık kullanılan çubuÄŸuna sürükleyin)" }, "translation" : { "value" : "Çeviri", @@ -158,7 +158,7 @@ "open_next_entry" : "sonraki öğeyi görüntüle", "open_previous_entry" : "önceki öğeyi görüntüle", "spacebar" : "space/shift+space ", - "move_page_down_up" : "moves the page down/up ", + "move_page_down_up" : "sayfayı aÅŸağı/yukarı hareket ettir", "focus_next_entry" : "sonraki öğeyi görüntülemeden iÅŸaretle", "focus_previous_entry" : "önceki öğeyi görüntülemeden iÅŸaretle", "open_next_feed" : "sonraki aboneliÄŸi veya kategoriyi görüntüle", @@ -170,10 +170,10 @@ "mark_current_entry" : "görüntülenen öğeyi okundu/okunmadı iÅŸaretle", "mark_all_as_read" : "tümünü okundu iÅŸaretle", "open_in_new_tab_mark_as_read" : "öğeyi yeni bir sekmede aç ve okundu iÅŸaretle", - "fullscreen" : "toggle full screen mode ", - "font_size" : "increase/decrease font size of the current entry ", - "go_to_all" : "go to the All view ", - "go_to_starred" : "go to the Starred view ", + "fullscreen" : "tam ekran moduna geç ", + "font_size" : "mevcut içerik için yazı boyunutunu arttır/azalt", + "go_to_all" : "Tüm öğeleri görüntüle", + "go_to_starred" : "yıldızlı öğerleri görüntüle", "feed_search" : "abonelik ismini yazarak aboneliÄŸe git" } } From 73a77183aa2ef7cac61e8018be1b1b5077804540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ak=C4=B1n=20Ayturan?= Date: Sun, 14 Dec 2014 13:09:35 +0200 Subject: [PATCH 110/241] Update tr.js --- src/main/app/i18n/tr.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/app/i18n/tr.js b/src/main/app/i18n/tr.js index 89680295..2869ee3e 100644 --- a/src/main/app/i18n/tr.js +++ b/src/main/app/i18n/tr.js @@ -56,7 +56,7 @@ "donate" : "Bağış" }, "view" : { - "entry_source" : "site: ", + "entry_source" : "kaynak: ", "entry_author" : "yazar: ", "error_while_loading_feed" : "Bu aboneliÄŸi çekerken hata oluÅŸtu.", "keep_unread" : "Okunmadı olarak sakla", From 7879f66e784546325b775bd1cd7f22e9a09b023e Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 15 Dec 2014 10:48:52 +0100 Subject: [PATCH 111/241] test for html entities --- src/test/java/com/commafeed/backend/feed/FeedUtilsTest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/test/java/com/commafeed/backend/feed/FeedUtilsTest.java b/src/test/java/com/commafeed/backend/feed/FeedUtilsTest.java index 7115f330..e7dd4710 100644 --- a/src/test/java/com/commafeed/backend/feed/FeedUtilsTest.java +++ b/src/test/java/com/commafeed/backend/feed/FeedUtilsTest.java @@ -62,4 +62,10 @@ public class FeedUtilsTest { Assert.assertEquals("UTF-8", FeedUtils.extractDeclaredEncoding("".getBytes())); Assert.assertEquals("UTF-8", FeedUtils.extractDeclaredEncoding("".getBytes())); } + + @Test + public void testReplaceHtmlEntitiesWithNumericEntities() { + String source = "T´l´phone ′"; + Assert.assertEquals("T´l´phone ′", FeedUtils.replaceHtmlEntitiesWithNumericEntities(source)); + } } From 3cbbb67b0c3351f72d4631b3fa07009ccfde2f6c Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 15 Dec 2014 11:16:22 +0100 Subject: [PATCH 112/241] memory optimizations --- src/main/java/com/commafeed/backend/feed/FeedUtils.java | 7 +------ .../java/com/commafeed/backend/feed/HtmlEntities.java | 9 +++++---- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/commafeed/backend/feed/FeedUtils.java b/src/main/java/com/commafeed/backend/feed/FeedUtils.java index 6347051b..c236a29b 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedUtils.java +++ b/src/main/java/com/commafeed/backend/feed/FeedUtils.java @@ -10,7 +10,6 @@ import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -133,11 +132,7 @@ public class FeedUtils { } public static String replaceHtmlEntitiesWithNumericEntities(String source) { - String result = source; - for (Map.Entry entry : HtmlEntities.NUMERIC_MAPPING.entrySet()) { - result = StringUtils.replace(result, entry.getKey(), entry.getValue()); - } - return result; + return StringUtils.replaceEach(source, HtmlEntities.HTML_ENTITIES, HtmlEntities.NUMERIC_ENTITIES); } /** diff --git a/src/main/java/com/commafeed/backend/feed/HtmlEntities.java b/src/main/java/com/commafeed/backend/feed/HtmlEntities.java index 6c6df31c..3e927c67 100644 --- a/src/main/java/com/commafeed/backend/feed/HtmlEntities.java +++ b/src/main/java/com/commafeed/backend/feed/HtmlEntities.java @@ -1,13 +1,13 @@ package com.commafeed.backend.feed; -import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; public class HtmlEntities { - public static final Map NUMERIC_MAPPING = Collections.unmodifiableMap(loadMap()); + public static final String[] HTML_ENTITIES; + public static final String[] NUMERIC_ENTITIES; - private static synchronized Map loadMap() { + static { Map map = new LinkedHashMap<>(); map.put("Á", "Á"); map.put("á", "á"); @@ -260,6 +260,7 @@ public class HtmlEntities { map.put("‍", "‍"); map.put("‌", "‌"); - return map; + HTML_ENTITIES = map.keySet().toArray(new String[map.size()]); + NUMERIC_ENTITIES = map.values().toArray(new String[map.size()]); } } From eee0b949de6f3a2fb5c524ad8b6d185d6d340d2e Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 15 Dec 2014 15:54:39 +0100 Subject: [PATCH 113/241] coherent logging string --- .../com/commafeed/backend/service/DatabaseCleaningService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/commafeed/backend/service/DatabaseCleaningService.java b/src/main/java/com/commafeed/backend/service/DatabaseCleaningService.java index e5eb385d..55805b59 100644 --- a/src/main/java/com/commafeed/backend/service/DatabaseCleaningService.java +++ b/src/main/java/com/commafeed/backend/service/DatabaseCleaningService.java @@ -94,7 +94,7 @@ public class DatabaseCleaningService { deleted = UnitOfWork.run(sessionFactory, () -> feedEntryStatusDAO.delete(feedEntryStatusDAO.getOldStatuses(olderThan, BATCH_SIZE))); total += deleted; - log.info("cleaned {} old read statuses", total); + log.info("removed {} old read statuses", total); } while (deleted != 0); log.info("cleanup done: {} old read statuses deleted", total); return total; From 71727202f3243c92761df01f02ba905c3d67dd92 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 15 Dec 2014 16:01:27 +0100 Subject: [PATCH 114/241] h2 upgrade --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c6607fda..981fbe66 100644 --- a/pom.xml +++ b/pom.xml @@ -326,7 +326,7 @@ com.h2database h2 - 1.4.182 + 1.4.183 mysql From 18058c2a36546bc05e7abcd9b4c3201f4d4a423b Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 15 Dec 2014 16:02:47 +0100 Subject: [PATCH 115/241] slf4j upgrade --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 981fbe66..b7e5e283 100644 --- a/pom.xml +++ b/pom.xml @@ -170,7 +170,7 @@ org.slf4j slf4j-api - 1.7.7 + 1.7.8 From d384c0a1419a2e0cae3390a5d314f491c618c33a Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 15 Dec 2014 16:02:54 +0100 Subject: [PATCH 116/241] mockito upgrade --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b7e5e283..8f0fd885 100644 --- a/pom.xml +++ b/pom.xml @@ -353,7 +353,7 @@ org.mockito mockito-core - 1.10.8 + 1.10.16 test From 23aa5fa0a3225ecdf641b0cfeebf7e41dd79244d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ak=C4=B1n=20Ayturan?= Date: Tue, 16 Dec 2014 09:08:14 +0200 Subject: [PATCH 117/241] Update tr.js when i see on the site :) --- src/main/app/i18n/tr.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/app/i18n/tr.js b/src/main/app/i18n/tr.js index 2869ee3e..bf4a91ac 100644 --- a/src/main/app/i18n/tr.js +++ b/src/main/app/i18n/tr.js @@ -44,10 +44,10 @@ "titles_only" : "Sadece baÅŸlıklar", "expanded_view" : "GeniÅŸletilmiÅŸ görünüm", "mark_all_as_read" : "Tümünü okundu iÅŸaretle", - "mark_all_older_12_hours" : "12 saatten daha eski öğeler ", - "mark_all_older_day" : "Bir günden eski yazılar", - "mark_all_older_week" : "Bir haftadan eski yazılar", - "mark_all_older_two_weeks" : "İki haftadan eski yazılar", + "mark_all_older_12_hours" : "12 saatten daha eski yayınlar ", + "mark_all_older_day" : "Bir günden eski yayınlar", + "mark_all_older_week" : "Bir haftadan eski yayınlar", + "mark_all_older_two_weeks" : "İki haftadan eski yayınlar", "settings" : "Ayarlar", "profile" : "Profil", "admin" : "Yönetim", @@ -61,7 +61,7 @@ "error_while_loading_feed" : "Bu aboneliÄŸi çekerken hata oluÅŸtu.", "keep_unread" : "Okunmadı olarak sakla", "no_unread_items" : "Okunmamış ileti yok.", - "mark_up_to_here" : "Mark as read up to here ", + "mark_up_to_here" : "Buraya kadar olan bütün yayınları okundu olarak iÅŸaretle!", "search_for" : "searching for: ", "no_search_results" : "İstenen anahtar kelimeler için eÅŸleÅŸme bulunamadı" }, From de7516116df09748e8f5c4396f4a54d9a237097e Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 18 Dec 2014 09:37:47 +0100 Subject: [PATCH 118/241] deps upgrade --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 8f0fd885..4b452f1b 100644 --- a/pom.xml +++ b/pom.xml @@ -170,7 +170,7 @@ org.slf4j slf4j-api - 1.7.8 + 1.7.9 @@ -353,7 +353,7 @@ org.mockito mockito-core - 1.10.16 + 1.10.17 test From c164926c541aea3d9c40ae9515caa5e1a79a0e09 Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 18 Dec 2014 10:13:44 +0100 Subject: [PATCH 119/241] rewrite using lambda --- src/main/java/com/commafeed/backend/feed/FeedQueues.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/main/java/com/commafeed/backend/feed/FeedQueues.java b/src/main/java/com/commafeed/backend/feed/FeedQueues.java index a5be7f9f..8af0cfc4 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedQueues.java +++ b/src/main/java/com/commafeed/backend/feed/FeedQueues.java @@ -78,13 +78,7 @@ public class FeedQueues { public void add(Feed feed, boolean urgent) { int refreshInterval = config.getApplicationSettings().getRefreshIntervalMinutes(); if (feed.getLastUpdated() == null || feed.getLastUpdated().before(DateUtils.addMinutes(new Date(), -1 * refreshInterval))) { - boolean alreadyQueued = false; - for (FeedRefreshContext context : addQueue) { - if (context.getFeed().getId().equals(feed.getId())) { - alreadyQueued = true; - break; - } - } + boolean alreadyQueued = addQueue.stream().anyMatch(c -> c.getFeed().getId().equals(feed.getId())); if (!alreadyQueued) { addQueue.add(new FeedRefreshContext(feed, urgent)); } From 68cb8e194d53c3db1c752f823263ff99e0f743b2 Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 18 Dec 2014 14:09:15 +0100 Subject: [PATCH 120/241] rewrite using lambda --- src/main/java/com/commafeed/backend/feed/FeedQueues.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/commafeed/backend/feed/FeedQueues.java b/src/main/java/com/commafeed/backend/feed/FeedQueues.java index 8af0cfc4..a310bfc9 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedQueues.java +++ b/src/main/java/com/commafeed/backend/feed/FeedQueues.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Map; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Singleton; @@ -129,10 +130,7 @@ public class FeedQueues { } // update all feeds in the database - List feeds = new ArrayList<>(); - for (FeedRefreshContext context : map.values()) { - feeds.add(context.getFeed()); - } + List feeds = map.values().stream().map(c -> c.getFeed()).collect(Collectors.toList()); feedDAO.merge(feeds); } From a96862fffadb413710e2acbd8298c6c5be2811f2 Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 18 Dec 2014 16:31:35 +0100 Subject: [PATCH 121/241] more tests --- .../backend/service/FeedEntryFilteringService.java | 5 ----- .../service/FeedEntryFilteringServiceTest.java | 13 ++++++++++++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/commafeed/backend/service/FeedEntryFilteringService.java b/src/main/java/com/commafeed/backend/service/FeedEntryFilteringService.java index 038d5393..bfb7d4ff 100644 --- a/src/main/java/com/commafeed/backend/service/FeedEntryFilteringService.java +++ b/src/main/java/com/commafeed/backend/service/FeedEntryFilteringService.java @@ -38,11 +38,6 @@ public class FeedEntryFilteringService { private static JexlEngine initEngine() { // classloader that prevents object creation ClassLoader cl = new ClassLoader() { - @Override - public Class loadClass(String name) throws ClassNotFoundException { - return null; - } - @Override protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { return null; diff --git a/src/test/java/com/commafeed/backend/service/FeedEntryFilteringServiceTest.java b/src/test/java/com/commafeed/backend/service/FeedEntryFilteringServiceTest.java index 8babc731..8feda310 100644 --- a/src/test/java/com/commafeed/backend/service/FeedEntryFilteringServiceTest.java +++ b/src/test/java/com/commafeed/backend/service/FeedEntryFilteringServiceTest.java @@ -42,7 +42,7 @@ public class FeedEntryFilteringServiceTest { @Test public void simpleExpression() throws FeedEntryFilterException { - Assert.assertTrue(service.filterMatchesEntry("author eq 'athou'", entry)); + Assert.assertTrue(service.filterMatchesEntry("author.toString() eq 'athou'", entry)); } @Test(expected = FeedEntryFilterException.class) @@ -67,8 +67,19 @@ public class FeedEntryFilteringServiceTest { @Test public void handlesNullCorrectly() throws FeedEntryFilterException { + entry.setUrl(null); entry.setContent(new FeedEntryContent()); service.filterMatchesEntry("author eq 'athou'", entry); } + @Test(expected = FeedEntryFilterException.class) + public void incorrectScriptThrowsException() throws FeedEntryFilterException { + service.filterMatchesEntry("aa eqz bb", entry); + } + + @Test(expected = FeedEntryFilterException.class) + public void incorrectReturnTypeThrowsException() throws FeedEntryFilterException { + service.filterMatchesEntry("1", entry); + } + } From 1f2e35060b1b5414adbaa18de21691722adead2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ak=C4=B1n=20Ayturan?= Date: Sun, 28 Dec 2014 16:14:49 +0200 Subject: [PATCH 122/241] Update README.md doomrobo/CommaFeed-Android-Reader has been discontinued. - ( https://github.com/doomrobo/CommaFeed-Android-Reader) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bea55f9a..e849deba 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Google Reader inspired self-hosted RSS reader, based on Dropwizard and AngularJS ## Related open-source projects -Android apps: [News+ extension](https://github.com/Athou/commafeed-newsplus) - [Android app](https://github.com/doomrobo/CommaFeed-Android-Reader) +Android apps: [News+ extension](https://github.com/Athou/commafeed-newsplus) Browser extensions: [Chrome](https://github.com/Athou/commafeed-chrome) - [Firefox](https://github.com/Athou/commafeed-firefox) - [Opera](https://github.com/Athou/commafeed-opera) - [Safari](https://github.com/Athou/commafeed-safari) From 3dcb8590f60732135897bf32fd8c05597b959fd2 Mon Sep 17 00:00:00 2001 From: Athou Date: Sat, 3 Jan 2015 10:30:40 +0100 Subject: [PATCH 123/241] project modernized, no longer needs to scan during every build --- pom.xml | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/pom.xml b/pom.xml index 4b452f1b..8cc91ead 100644 --- a/pom.xml +++ b/pom.xml @@ -140,23 +140,6 @@ - - org.gaul - modernizer-maven-plugin - 1.1.0 - - ${java.version} - - - - modernizer - compile - - modernizer - - - - From 06151eab3b95cb2492d9f94d169327daa1bdedb3 Mon Sep 17 00:00:00 2001 From: Athou Date: Sat, 3 Jan 2015 10:32:09 +0100 Subject: [PATCH 124/241] dependencies upgrade --- pom.xml | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 8cc91ead..3b8be946 100644 --- a/pom.xml +++ b/pom.xml @@ -214,16 +214,12 @@ com.wordnik swagger-jaxrs_2.10 - 1.3.11 + 1.3.12 javax.ws.rs jsr311-api - - commons-lang - commons-lang - com.fasterxml.jackson.module jackson-module-scala_2.10 @@ -266,13 +262,13 @@ org.apache.commons commons-math3 - 3.3 + 3.4 redis.clients jedis - 2.6.1 + 2.6.2 com.sun.mail @@ -309,7 +305,7 @@ com.h2database h2 - 1.4.183 + 1.4.184 mysql @@ -336,7 +332,7 @@ org.mockito mockito-core - 1.10.17 + 2.0.2-beta test From 04c0833111a1af824d563bfd42b339428553d9b3 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 5 Jan 2015 14:56:31 +0100 Subject: [PATCH 125/241] run bower during maven build --- gulpfile.js | 21 +++++---------------- package.json | 2 +- pom.xml | 11 ++++++++++- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 63e58680..e2403641 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -4,7 +4,6 @@ var revReplace = require('gulp-rev-replace'); var minifyCSS = require('gulp-minify-css'); var uglify = require('gulp-uglify'); var filter = require('gulp-filter'); -var bower = require('gulp-bower'); var connect = require('gulp-connect'); var modRewrite = require('connect-modrewrite'); var sass = require('gulp-sass'); @@ -15,16 +14,6 @@ var SRC_DIR = 'src/main/app/'; var TEMP_DIR = 'target/gulp/' var BUILD_DIR = 'target/classes/assets/'; -gulp.task('bower_prune', function() { - return bower({ - cmd : 'prune' - }); -}); - -gulp.task('bower', ['bower_prune'], function() { - return bower(); -}); - gulp.task('images', function() { return gulp.src(SRC_DIR + 'images/**/*').pipe(gulp.dest(BUILD_DIR + 'images')); }); @@ -44,20 +33,20 @@ gulp.task('sass', function() { return gulp.src(SRC_DIR + 'sass/app.scss').pipe(sass()).pipe(gulp.dest(TEMP_DIR + 'css')); }); -gulp.task('fonts', ['bower'], function() { +gulp.task('fonts', function() { var font_awesome = SRC_DIR + 'lib/font-awesome/font/fontawesome-webfont.*'; var zocial = SRC_DIR + 'lib/zocial-less/css/zocial-regular-*'; var readabilicons = SRC_DIR + 'lib/readabilicons/webfont/fonts/readabilicons-*'; return gulp.src([font_awesome, zocial, readabilicons]).pipe(gulp.dest(BUILD_DIR + 'font')); }); -gulp.task('select2', ['bower'], function() { +gulp.task('select2', function() { var gif = SRC_DIR + 'lib/select2/*.gif'; var png = SRC_DIR + 'lib/select2/*.png'; return gulp.src([gif, png]).pipe(gulp.dest(BUILD_DIR + 'css')); }); -gulp.task('swagger-ui', ['bower'], function() { +gulp.task('swagger-ui', function() { var index_html = SRC_DIR + 'api/index.html'; var lib = SRC_DIR + 'lib/swagger-ui/dist/**/*'; return gulp.src([lib, index_html]).pipe(gulp.dest(BUILD_DIR + 'api')); @@ -71,7 +60,7 @@ gulp.task('template-cache', function() { return gulp.src(SRC_DIR + 'templates/**/*.html').pipe(templateCache(options)).pipe(gulp.dest(TEMP_DIR + 'js')); }); -gulp.task('build-dev', ['images', 'i18n', 'favicons', 'sass', 'fonts', 'select2', 'swagger-ui', 'template-cache', 'bower'], function() { +gulp.task('build-dev', ['images', 'i18n', 'favicons', 'sass', 'fonts', 'select2', 'swagger-ui', 'template-cache'], function() { var assets = useref.assets({ searchPath : [SRC_DIR, TEMP_DIR] }); @@ -81,7 +70,7 @@ gulp.task('build-dev', ['images', 'i18n', 'favicons', 'sass', 'fonts', 'select2' revReplace()).pipe(gulp.dest(BUILD_DIR)).pipe(connect.reload()); }); -gulp.task('build', ['images', 'i18n', 'favicons', 'sass', 'fonts', 'select2', 'swagger-ui', 'template-cache', 'bower'], function() { +gulp.task('build', ['images', 'i18n', 'favicons', 'sass', 'fonts', 'select2', 'swagger-ui', 'template-cache'], function() { var assets = useref.assets({ searchPath : [SRC_DIR, TEMP_DIR] }); diff --git a/package.json b/package.json index addc4492..6f933df5 100644 --- a/package.json +++ b/package.json @@ -4,13 +4,13 @@ "main": "main.js", "private": true, "devDependencies": { + "bower": "1.3.12", "gulp": "3.8.10", "gulp-rev": "2.0.1", "gulp-rev-replace": "0.3.1", "gulp-minify-css": "0.3.11", "gulp-uglify": "1.0.2", "gulp-filter": "1.0.2", - "gulp-bower": "0.0.7", "gulp-connect": "2.2.0", "connect-modrewrite": "0.7.9", "gulp-sass": "1.2.4", diff --git a/pom.xml b/pom.xml index 3b8be946..75e89b1f 100644 --- a/pom.xml +++ b/pom.xml @@ -99,7 +99,7 @@ com.github.eirslett frontend-maven-plugin - 0.0.19 + 0.0.20 install node and npm @@ -119,6 +119,15 @@ generate-resources + + bower install + + bower + + + install + + gulp build From 90e680d6be40cd401cf971ff4a9074eb3724b7ad Mon Sep 17 00:00:00 2001 From: Athou Date: Tue, 6 Jan 2015 09:27:18 +0100 Subject: [PATCH 126/241] upgrade jdom to 2.0.5 for performance reasons (https://github.com/hunterhacker/jdom/issues/112) --- pom.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pom.xml b/pom.xml index 75e89b1f..5bb9e19a 100644 --- a/pom.xml +++ b/pom.xml @@ -285,16 +285,29 @@ 1.5.2 + com.rometools rome ${rome.version} + + + jdom + org.jdom + + com.rometools rome-opml ${rome.version} + + org.jdom + jdom2 + 2.0.5 + + org.jsoup jsoup From c81cc8bea4ce74d81a16258853bb93691ea89124 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 12 Jan 2015 09:56:34 +0100 Subject: [PATCH 127/241] fix relative url detection (#699) --- src/main/java/com/commafeed/backend/feed/FeedUtils.java | 8 ++------ .../java/com/commafeed/backend/feed/FeedUtilsTest.java | 7 +++++++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/commafeed/backend/feed/FeedUtils.java b/src/main/java/com/commafeed/backend/feed/FeedUtils.java index c236a29b..0999e85e 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedUtils.java +++ b/src/main/java/com/commafeed/backend/feed/FeedUtils.java @@ -430,12 +430,8 @@ public class FeedUtils { } public static boolean isRelative(final String url) { - // the regex means "doesn't start with 'scheme://'" - if ((url != null) && (url.startsWith("/") == false) && (!url.matches("^\\w+\\:\\/\\/.*")) && !(url.startsWith("#"))) { - return true; - } else { - return false; - } + // the regex means "start with 'scheme://'" + return url.startsWith("/") || url.startsWith("#") || !url.matches("^\\w+\\:\\/\\/.*"); } public static String getFaviconUrl(FeedSubscription subscription, String publicUrl) { diff --git a/src/test/java/com/commafeed/backend/feed/FeedUtilsTest.java b/src/test/java/com/commafeed/backend/feed/FeedUtilsTest.java index e7dd4710..de41be4b 100644 --- a/src/test/java/com/commafeed/backend/feed/FeedUtilsTest.java +++ b/src/test/java/com/commafeed/backend/feed/FeedUtilsTest.java @@ -44,11 +44,18 @@ public class FeedUtilsTest { @Test public void testToAbsoluteUrl() { String expected = "http://a.com/blog/entry/1"; + + // usual cases Assert.assertEquals(expected, FeedUtils.toAbsoluteUrl("http://a.com/blog/entry/1", "http://a.com/feed/", "http://a.com/feed/")); Assert.assertEquals(expected, FeedUtils.toAbsoluteUrl("http://a.com/blog/entry/1", "http://a.com/feed", "http://a.com/feed")); + + // relative links Assert.assertEquals(expected, FeedUtils.toAbsoluteUrl("../blog/entry/1", "http://a.com/feed/", "http://a.com/feed/")); Assert.assertEquals(expected, FeedUtils.toAbsoluteUrl("../blog/entry/1", "feed.xml", "http://a.com/feed/feed.xml")); + // root-relative links + Assert.assertEquals(expected, FeedUtils.toAbsoluteUrl("/blog/entry/1", "/feed", "http://a.com/feed")); + Assert.assertEquals("http://ergoemacs.org/emacs/elisp_all_about_lines.html", FeedUtils.toAbsoluteUrl("elisp_all_about_lines.html", "blog.xml", "http://ergoemacs.org/emacs/blog.xml")); From 9d64426b00a973ce2758116816b119102626f6a7 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 12 Jan 2015 09:56:55 +0100 Subject: [PATCH 128/241] add testcase (#699) --- src/test/java/com/commafeed/backend/feed/FeedUtilsTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/test/java/com/commafeed/backend/feed/FeedUtilsTest.java b/src/test/java/com/commafeed/backend/feed/FeedUtilsTest.java index de41be4b..846cd485 100644 --- a/src/test/java/com/commafeed/backend/feed/FeedUtilsTest.java +++ b/src/test/java/com/commafeed/backend/feed/FeedUtilsTest.java @@ -56,6 +56,9 @@ public class FeedUtilsTest { // root-relative links Assert.assertEquals(expected, FeedUtils.toAbsoluteUrl("/blog/entry/1", "/feed", "http://a.com/feed")); + // real cases + Assert.assertEquals("https://github.com/erusev/parsedown/releases/tag/1.3.0", FeedUtils.toAbsoluteUrl( + "/erusev/parsedown/releases/tag/1.3.0", "/erusev/parsedown/releases", "https://github.com/erusev/parsedown/tags.atom")); Assert.assertEquals("http://ergoemacs.org/emacs/elisp_all_about_lines.html", FeedUtils.toAbsoluteUrl("elisp_all_about_lines.html", "blog.xml", "http://ergoemacs.org/emacs/blog.xml")); From c4185034e44f6e6fb3c11e21326b15d5ec4c2420 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 12 Jan 2015 09:57:30 +0100 Subject: [PATCH 129/241] urlAfterRedirect was always null (#699) --- src/main/java/com/commafeed/backend/feed/FeedFetcher.java | 4 ++-- src/main/java/com/commafeed/backend/feed/FeedParser.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/commafeed/backend/feed/FeedFetcher.java b/src/main/java/com/commafeed/backend/feed/FeedFetcher.java index 0929b72f..8ba02971 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedFetcher.java +++ b/src/main/java/com/commafeed/backend/feed/FeedFetcher.java @@ -41,7 +41,7 @@ public class FeedFetcher { byte[] content = result.getContent(); try { - fetchedFeed = parser.parse(feedUrl, content); + fetchedFeed = parser.parse(result.getUrlAfterRedirect(), content); } catch (FeedException e) { if (extractFeedUrlFromHtml) { String extractedUrl = extractFeedUrl(StringUtils.newStringUtf8(result.getContent()), feedUrl); @@ -50,7 +50,7 @@ public class FeedFetcher { result = getter.getBinary(extractedUrl, lastModified, eTag, timeout); content = result.getContent(); - fetchedFeed = parser.parse(feedUrl, content); + fetchedFeed = parser.parse(result.getUrlAfterRedirect(), content); } else { throw e; } diff --git a/src/main/java/com/commafeed/backend/feed/FeedParser.java b/src/main/java/com/commafeed/backend/feed/FeedParser.java index d74ab8aa..ac49b254 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedParser.java +++ b/src/main/java/com/commafeed/backend/feed/FeedParser.java @@ -77,7 +77,7 @@ public class FeedParser { } entry.setGuid(FeedUtils.truncate(guid, 2048)); entry.setUpdated(validateDate(getEntryUpdateDate(item), true)); - entry.setUrl(FeedUtils.truncate(FeedUtils.toAbsoluteUrl(item.getLink(), feed.getLink(), feed.getUrlAfterRedirect()), 2048)); + entry.setUrl(FeedUtils.truncate(FeedUtils.toAbsoluteUrl(item.getLink(), feed.getLink(), feedUrl), 2048)); // if link is empty but guid is used as url if (StringUtils.isBlank(entry.getUrl()) && StringUtils.startsWith(entry.getGuid(), "http")) { From b87a18b993041c828f0a4a67db901d26c9823f49 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 12 Jan 2015 10:14:40 +0100 Subject: [PATCH 130/241] various upgrades --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 5bb9e19a..954e1eb9 100644 --- a/pom.xml +++ b/pom.xml @@ -162,7 +162,7 @@ org.slf4j slf4j-api - 1.7.9 + 1.7.10 @@ -206,7 +206,7 @@ org.liquibase liquibase-core - 3.3.1 + 3.3.2 io.dropwizard @@ -271,7 +271,7 @@ org.apache.commons commons-math3 - 3.4 + 3.4.1 From d87a5b14f8952684eca6150064d215183f311b7f Mon Sep 17 00:00:00 2001 From: Athou Date: Sun, 18 Jan 2015 07:57:00 +0100 Subject: [PATCH 131/241] dropwizard upgrade --- pom.xml | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/pom.xml b/pom.xml index 954e1eb9..3eeb1f5c 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ UTF-8 1.8 - 0.8.0-rc1 + 0.8.0-rc2 4.0-beta5 3.6.0 1.5.0 @@ -165,12 +165,6 @@ 1.7.10 - - org.apache.commons - commons-jexl - 2.1.1 - - com.google.inject guice @@ -202,12 +196,6 @@ dropwizard-migrations ${dropwizard.version} - - - org.liquibase - liquibase-core - 3.3.2 - io.dropwizard dropwizard-assets @@ -253,6 +241,11 @@ ${querydsl.version} + + com.google.guava + guava + 18.0 + commons-io commons-io @@ -273,6 +266,11 @@ commons-math3 3.4.1 + + org.apache.commons + commons-jexl + 2.1.1 + redis.clients @@ -327,7 +325,7 @@ com.h2database h2 - 1.4.184 + 1.4.185 mysql From 10af873fa50b656884e156fa7db5b19c8f5f8d75 Mon Sep 17 00:00:00 2001 From: Athou Date: Tue, 20 Jan 2015 09:55:21 +0100 Subject: [PATCH 132/241] build deps upgrade --- package.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 6f933df5..6f652a7d 100644 --- a/package.json +++ b/package.json @@ -6,15 +6,15 @@ "devDependencies": { "bower": "1.3.12", "gulp": "3.8.10", - "gulp-rev": "2.0.1", + "gulp-rev": "3.0.0", "gulp-rev-replace": "0.3.1", - "gulp-minify-css": "0.3.11", - "gulp-uglify": "1.0.2", - "gulp-filter": "1.0.2", + "gulp-minify-css": "0.3.13", + "gulp-uglify": "1.1.0", + "gulp-filter": "2.0.0", "gulp-connect": "2.2.0", "connect-modrewrite": "0.7.9", - "gulp-sass": "1.2.4", - "gulp-useref": "1.0.2", - "gulp-angular-templatecache": "1.4.2" + "gulp-sass": "1.3.2", + "gulp-useref": "1.1.1", + "gulp-angular-templatecache": "1.5.0" } } From f2b80bdc08bc30e9b26d62012b39b593f40de71d Mon Sep 17 00:00:00 2001 From: Athou Date: Wed, 21 Jan 2015 12:31:54 +0100 Subject: [PATCH 133/241] build on openshift using jdk8 --- .openshift/action_hooks/build | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.openshift/action_hooks/build b/.openshift/action_hooks/build index 5d848806..f472a7e5 100755 --- a/.openshift/action_hooks/build +++ b/.openshift/action_hooks/build @@ -1,6 +1,15 @@ #!/bin/bash -cd $OPENSHIFT_REPO_DIR +if [ ! -d $OPENSHIFT_DATA_DIR/jdk1.8.0_20 ] + then + cd $OPENSHIFT_DATA_DIR + wget http://www.java.net/download/jdk8u20/archive/b17/binaries/jdk-8u20-ea-bin-b17-linux-x64-04_jun_2014.tar.gz + tar xvf *.tar.gz + rm -f *.tar.gz +fi +export JAVA_HOME=$OPENSHIFT_DATA_DIR/jdk1.8.0_20 +export PATH=$JAVA_HOME/bin:$PATH +cd $OPENSHIFT_REPO_DIR rm -rf $OPENSHIFT_REPO_DIR/node rm -rf $OPENSHIFT_REPO_DIR/node_modules rm -rf $OPENSHIFT_TMP_DIR/npm From 655e20e99ec0f511f045f537ef89682f436bfc9f Mon Sep 17 00:00:00 2001 From: Athou Date: Wed, 21 Jan 2015 14:20:19 +0100 Subject: [PATCH 134/241] use openshift nexus mirror --- .openshift/action_hooks/build | 2 +- .openshift/settings.xml | 40 ++++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/.openshift/action_hooks/build b/.openshift/action_hooks/build index f472a7e5..465f7eff 100755 --- a/.openshift/action_hooks/build +++ b/.openshift/action_hooks/build @@ -28,4 +28,4 @@ export NPM_CONFIG_ARCH="x64" npm install npm export PATH="$OPENSHIFT_REPO_DIR/node_modules/.bin:$PATH" -mvn clean package -DskipTests -Dos.arch=x64 +mvn clean package -DskipTests -Dos.arch=x64 -s .openshift/settings.xml diff --git a/.openshift/settings.xml b/.openshift/settings.xml index 8cfcb0a7..397d208f 100644 --- a/.openshift/settings.xml +++ b/.openshift/settings.xml @@ -1,3 +1,41 @@ - $OPENSHIFT_DATA_DIR + + + nexus + central + http://mirror1.ops.rhcloud.com/nexus/content/groups/public + + + + + nexus + + + central + http://central + + true + + + true + + + + + + central + http://central + + true + + + true + + + + + + + nexus + From 6dfce2ca3070fd495f5a8e8b675b0fa7bbcdcdd7 Mon Sep 17 00:00:00 2001 From: Athou Date: Wed, 21 Jan 2015 15:10:45 +0100 Subject: [PATCH 135/241] use recent version of maven --- .openshift/action_hooks/build | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.openshift/action_hooks/build b/.openshift/action_hooks/build index 465f7eff..def1fc2d 100755 --- a/.openshift/action_hooks/build +++ b/.openshift/action_hooks/build @@ -6,8 +6,16 @@ if [ ! -d $OPENSHIFT_DATA_DIR/jdk1.8.0_20 ] tar xvf *.tar.gz rm -f *.tar.gz fi +if [ ! -d $OPENSHIFT_DATA_DIR/apache-maven-3.2.3 ] + then + cd $OPENSHIFT_DATA_DIR + wget http://archive.apache.org/dist/maven/maven-3/3.2.3/binaries/apache-maven-3.2.3-bin.tar.gz + tar xvf *.tar.gz + rm -f *.tar.gz +fi +export M2=$OPENSHIFT_DATA_DIR/apache-maven-3.2.3/bin export JAVA_HOME=$OPENSHIFT_DATA_DIR/jdk1.8.0_20 -export PATH=$JAVA_HOME/bin:$PATH +export PATH=$JAVA_HOME/bin:$M2:$PATH cd $OPENSHIFT_REPO_DIR rm -rf $OPENSHIFT_REPO_DIR/node From b660602809e0820d39a89233ec84b985cdfd2a9b Mon Sep 17 00:00:00 2001 From: Athou Date: Wed, 21 Jan 2015 15:42:03 +0100 Subject: [PATCH 136/241] node and npm upgrade --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 3eeb1f5c..e6108107 100644 --- a/pom.xml +++ b/pom.xml @@ -108,8 +108,8 @@ generate-resources - v0.10.30 - 1.3.8 + v0.10.35 + 2.2.0 From 33c62f08ca2ec8e94dd981e8c1f62d96ed2d0efa Mon Sep 17 00:00:00 2001 From: Athou Date: Wed, 21 Jan 2015 15:42:09 +0100 Subject: [PATCH 137/241] this is not needed --- .openshift/action_hooks/build | 3 --- 1 file changed, 3 deletions(-) diff --git a/.openshift/action_hooks/build b/.openshift/action_hooks/build index def1fc2d..9e117af7 100755 --- a/.openshift/action_hooks/build +++ b/.openshift/action_hooks/build @@ -33,7 +33,4 @@ export HOME="$OPENSHIFT_TMP_DIR/local" export NPM_CONFIG_ARCH="x64" -npm install npm -export PATH="$OPENSHIFT_REPO_DIR/node_modules/.bin:$PATH" - mvn clean package -DskipTests -Dos.arch=x64 -s .openshift/settings.xml From 66df421de241f57d988b5de04302181afdffee32 Mon Sep 17 00:00:00 2001 From: "Xose M." Date: Fri, 23 Jan 2015 08:36:22 +0100 Subject: [PATCH 138/241] Actualizado gl.js Galego (gl, gl_ES) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit novas entradas respecto a versión antiga --- src/main/app/i18n/gl.js | 46 ++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/main/app/i18n/gl.js b/src/main/app/i18n/gl.js index 210bff37..ab3a7e0f 100644 --- a/src/main/app/i18n/gl.js +++ b/src/main/app/i18n/gl.js @@ -31,7 +31,7 @@ }, "new_category" : { "name" : "Nome", - "parent" : "Pai" + "parent" : "Subcategoría de " }, "toolbar" : { "unread" : "Sen Ler", @@ -39,12 +39,12 @@ "previous_entry" : "Entrada Anterior", "next_entry" : "Próxima Entrada", "refresh" : "Actualizar", - "refresh_all" : "Force refresh all my feeds ", + "refresh_all" : "Forzar a actualización de todas as fontes ", "sort_by_asc_desc" : "Ordenar por data asc/desc", "titles_only" : "Só títulos", "expanded_view" : "Vista expandida", "mark_all_as_read" : "Marcar todos como lidos", - "mark_all_older_12_hours" : "Items older than 12 hours ", + "mark_all_older_12_hours" : "Elementos anteriores a 12 h. ", "mark_all_older_day" : "Artigos anteriores a un día", "mark_all_older_week" : "Artigos de máis de unha semana", "mark_all_older_two_weeks" : "Artigos de máis de dúas semanas", @@ -56,14 +56,14 @@ "donate" : "Doar" }, "view" : { - "entry_source" : "from ", - "entry_author" : "by ", + "entry_source" : "desde ", + "entry_author" : "por ", "error_while_loading_feed" : "Erro mentras se cargaba esta fonte", "keep_unread" : "Gardar non lidos", "no_unread_items" : "non ten elementos sen ler.", - "mark_up_to_here" : "Mark as read up to here ", - "search_for" : "searching for: ", - "no_search_results" : "No match found for the requested keywords " + "mark_up_to_here" : "Marcar como lidos ate aquí ", + "search_for" : "buscando por: ", + "no_search_results" : "Sen coincidencias para as palabras introducidas " }, "feedsearch" : { "hint" : "Escriba unha suscrición...", @@ -80,8 +80,8 @@ "scroll_marks" : "En vista expandida, o desplazamento polas entradas márcaas como lidas." }, "appearance" : "Aspecto", - "scroll_speed" : "Scrolling speed when navigating between entries (in milliseconds) ", - "scroll_speed_help" : "set to 0 to disable ", + "scroll_speed" : "Velocidade de desplazamento navegando entre entradas (en milisegundos) ", + "scroll_speed_help" : "escriba 0 para deshabilitar ", "theme" : "Decorado", "submit_your_theme" : "Envíe o seu decorado", "custom_css" : "CSS Personalizado" @@ -89,21 +89,21 @@ "details" : { "feed_details" : "Detalles de fontes", "url" : "URL", - "website" : "Website ", + "website" : "Sitio web ", "name" : "Nome", "category" : "Categoría", - "position" : "Position ", + "position" : "Posición ", "last_refresh" : "Última actualización", - "message" : "Last refresh message ", + "message" : "Última mensaxe da actualización ", "next_refresh" : "Próxima actualización", "queued_for_refresh" : "En cola para actualizar", "feed_url" : "URL da fonte", "generate_api_key_first" : "Antes debes xerar unha chave API no teu perfil.", "unsubscribe" : "Rematar suscripción", - "unsubscribe_confirmation" : "Are you sure you want to unsubscribe from this feed? ", - "delete_category_confirmation" : "Are you sure you want to delete this category? ", + "unsubscribe_confirmation" : "Seguro que queres desuscribirte de esta fonte? ", + "delete_category_confirmation" : "Seguro que queres eliminar esta categoría? ", "category_details" : "Detalles da categoría", - "tag_details" : "Tag details ", + "tag_details" : "Detalles da etiqueta ", "parent_category" : "Categoría principal" }, "profile" : { @@ -119,7 +119,7 @@ "generate_new_api_key_info" : "Ao cambiar o contrasinal xerarase unha nova chave API", "opml_export" : "Exportación de OPML", "delete_account" : "Eliminar conta", - "delete_account_confirmation" : "Delete your acount? There's no turning back! " + "delete_account_confirmation" : "Eliminar conta? Non hai volta atrás! " }, "about" : { "rest_api" : { @@ -158,7 +158,7 @@ "open_next_entry" : "abrir próxima entrada", "open_previous_entry" : "abrir entrada anterior", "spacebar" : "space/shift+space ", - "move_page_down_up" : "moves the page down/up ", + "move_page_down_up" : "move a páxina arriba/abaixo ", "focus_next_entry" : "Establecer o foco na próxima entrada sen abrila", "focus_previous_entry" : "Establecer o foco na entrada anterior sen abrila", "open_next_feed" : "abrir a seguinte fonte ou categoría", @@ -170,11 +170,11 @@ "mark_current_entry" : "marcar como lida/non lida a entrada actual", "mark_all_as_read" : "marcar todas as entradas como lidas", "open_in_new_tab_mark_as_read" : "abrir entrada nunha nova lapela e marcar como lida", - "fullscreen" : "toggle full screen mode ", - "font_size" : "increase/decrease font size of the current entry ", - "go_to_all" : "go to the All view ", - "go_to_starred" : "go to the Starred view ", + "fullscreen" : "habilita a pantalla completa ", + "font_size" : "aumenta/diminúe o tamaño da letra da entrada activa ", + "go_to_all" : "ir a vista TODOS", + "go_to_starred" : "ir a vista Destacados ", "feed_search" : "navegue ate unha suscrición introducindo o nome da suscrición" } } -} \ No newline at end of file +} From d6b35b00b98e3ae0ef79869294d6cbdc93f5233a Mon Sep 17 00:00:00 2001 From: Athou Date: Wed, 11 Feb 2015 10:22:08 +0100 Subject: [PATCH 139/241] postgresql tweaks --- .../commafeed/backend/model/FeedEntryContent.java | 3 +++ .../com/commafeed/backend/model/UserSettings.java | 3 +++ src/main/resources/changelogs/db.changelog-1.0.xml | 12 ++++++++++++ 3 files changed, 18 insertions(+) diff --git a/src/main/java/com/commafeed/backend/model/FeedEntryContent.java b/src/main/java/com/commafeed/backend/model/FeedEntryContent.java index a1398b7d..fe34967b 100644 --- a/src/main/java/com/commafeed/backend/model/FeedEntryContent.java +++ b/src/main/java/com/commafeed/backend/model/FeedEntryContent.java @@ -11,6 +11,8 @@ import javax.persistence.Table; import lombok.Getter; import lombok.Setter; +import org.hibernate.annotations.Type; + @Entity @Table(name = "FEEDENTRYCONTENTS") @SuppressWarnings("serial") @@ -26,6 +28,7 @@ public class FeedEntryContent extends AbstractModel { @Lob @Column(length = Integer.MAX_VALUE) + @Type(type = "org.hibernate.type.StringClobType") private String content; @Column(length = 40) diff --git a/src/main/java/com/commafeed/backend/model/UserSettings.java b/src/main/java/com/commafeed/backend/model/UserSettings.java index 70ee4418..4928ac2b 100644 --- a/src/main/java/com/commafeed/backend/model/UserSettings.java +++ b/src/main/java/com/commafeed/backend/model/UserSettings.java @@ -13,6 +13,8 @@ import javax.persistence.Table; import lombok.Getter; import lombok.Setter; +import org.hibernate.annotations.Type; + @Entity @Table(name = "USERSETTINGS") @SuppressWarnings("serial") @@ -59,6 +61,7 @@ public class UserSettings extends AbstractModel { @Lob @Column(length = Integer.MAX_VALUE) + @Type(type = "org.hibernate.type.StringClobType") private String customCss; @Column(name = "scroll_speed") diff --git a/src/main/resources/changelogs/db.changelog-1.0.xml b/src/main/resources/changelogs/db.changelog-1.0.xml index 00f4a385..13b38032 100644 --- a/src/main/resources/changelogs/db.changelog-1.0.xml +++ b/src/main/resources/changelogs/db.changelog-1.0.xml @@ -5,6 +5,7 @@ 7:6d3ad493d25dd9c50067e804efc9ffcc + 7:896a68c1651397288c40f717ce0397b4 @@ -56,6 +57,7 @@ 7:eccd6b37116ab35ee963aa46152e1ae5 + 7:ac622ab04aec79a7e5854d25511abaef @@ -76,6 +78,7 @@ 7:93155e15f0feabe936e1de35711bf85b + 7:c52f258e54d34156208cbfd2d8547fbd @@ -104,6 +107,7 @@ 7:2d9e82da5573ac551df31a13f3bc40e5 + 7:c3cc179801e812635b53849301a1a1d1 @@ -133,6 +137,7 @@ 7:a2d83b0f7d1bf97a7553e94dd6100edf + 7:1c45f6b6a6e7583dd4c090a4a3930758 @@ -154,6 +159,7 @@ 7:a9cf194a01c16b937a897aea934f09ae + 7:6a386e0b08e98bdba9ce55e26ab90eba @@ -181,6 +187,7 @@ 7:e3a44d2e0f774dcb4efe36702c8d5f3f + 7:604b2bb0b62b7f0529e50e63c2b2cf0c @@ -221,6 +228,7 @@ 7:36e92eac052c7d2ce0ef75e3ec2cdf8d + 7:248affcafffd2243f8b0d16750e17af0 @@ -249,6 +257,7 @@ 7:6d68765b2116ba88680d69c03b3cefd2 + 7:6112f92b437b4d0ecfcdf038fd04ed2f @@ -265,6 +274,7 @@ 7:eefc98cfa1b9bbf51fa6acd7a0d49c1b + 7:abbff58b88c8cebfb4548d17730a262d @@ -287,6 +297,7 @@ + 7:750e0990a8edebd0252df7d4adc7aa7c @@ -321,6 +332,7 @@ 7:985d6607a4350e032ea345d9a2f2f0c0 + 7:722eaff49d04d43c5b26da0929d3f707 From 5dad9c2eb8a035c27125ae86c11ed0aa95471a98 Mon Sep 17 00:00:00 2001 From: Athou Date: Wed, 11 Feb 2015 19:16:42 +0100 Subject: [PATCH 140/241] dependencies upgrade --- pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index e6108107..9d42ea04 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ 1.8 0.8.0-rc2 4.0-beta5 - 3.6.0 + 3.6.1 1.5.0 @@ -156,7 +156,7 @@ org.projectlombok lombok - 1.14.8 + 1.16.2 provided @@ -335,7 +335,7 @@ org.postgresql postgresql - 9.3-1102-jdbc41 + 9.4-1200-jdbc41 net.sourceforge.jtds @@ -352,7 +352,7 @@ org.mockito mockito-core - 2.0.2-beta + 2.0.3-beta test From 4a9dc7249fe0e5962bee673207be5788f2c326b0 Mon Sep 17 00:00:00 2001 From: Ebrahim Byagowi Date: Thu, 12 Feb 2015 23:30:11 +0000 Subject: [PATCH 141/241] Add fullscreen Android/iOS app capability * https://developer.chrome.com/multidevice/android/installtohomescreen * https://developer.apple.com/library/safari/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html --- src/main/app/index.html | 4 ++++ src/main/app/manifest.json | 31 +++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 src/main/app/manifest.json diff --git a/src/main/app/index.html b/src/main/app/index.html index fbad9134..833063e4 100644 --- a/src/main/app/index.html +++ b/src/main/app/index.html @@ -4,6 +4,10 @@ CommaFeed + + + + diff --git a/src/main/app/manifest.json b/src/main/app/manifest.json new file mode 100644 index 00000000..78489d88 --- /dev/null +++ b/src/main/app/manifest.json @@ -0,0 +1,31 @@ +{ + "name": "CommaFeed", + "icons": [ + { + "src": "app-icon-72.png", + "sizes": "72x72", + "type": "image/png", + "density": "1.5" + }, + { + "src": "app-icon-114.png", + "sizes": "96x96", + "type": "image/png", + "density": "2.0" + }, + { + "src": "app-icon-144.png", + "sizes": "144x144", + "type": "image/png", + "density": "3.0" + }, + { + "src": "app-icon-192.png", + "sizes": "192x192", + "type": "image/png", + "density": "4.0" + } + ], + "start_url": "/", + "display": "standalone" +} From 19118ea241c574cfe35c06de18dcc1fbbc732fa9 Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 13 Feb 2015 09:34:34 +0100 Subject: [PATCH 142/241] exclude slf4j simple --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index 9d42ea04..8fa7272d 100644 --- a/pom.xml +++ b/pom.xml @@ -336,6 +336,12 @@ org.postgresql postgresql 9.4-1200-jdbc41 + + + slf4j-simple + org.slf4j + + net.sourceforge.jtds From f3a9c8e0e2486c3c59a22eb10fc1d2eee42704c5 Mon Sep 17 00:00:00 2001 From: Athou Date: Tue, 17 Feb 2015 09:06:07 +0100 Subject: [PATCH 143/241] jdom and mockito upgrades --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 8fa7272d..d520dda1 100644 --- a/pom.xml +++ b/pom.xml @@ -303,7 +303,7 @@ org.jdom jdom2 - 2.0.5 + 2.0.6 @@ -358,7 +358,7 @@ org.mockito mockito-core - 2.0.3-beta + 2.0.4-beta test From 7bb65a5e76949ac71c2bb20bd27c9e2c029b517d Mon Sep 17 00:00:00 2001 From: Athou Date: Tue, 17 Feb 2015 16:51:37 +0100 Subject: [PATCH 144/241] fix indentation --- config.dev.yml | 7 +------ config.yml.example | 10 ++++------ 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/config.dev.yml b/config.dev.yml index cae2ff51..5330ee20 100644 --- a/config.dev.yml +++ b/config.dev.yml @@ -77,12 +77,7 @@ database: password: sa properties: charSet: UTF-8 - maxWaitForConnection: 1s - validationQuery: "/* CommaFeed Health Check */ SELECT 1" - minSize: 1 - maxSize: 50 - checkConnectionWhileIdle: true - maxConnectionAge: 30m + validationQuery: "/* CommaFeed Health Check */ SELECT 1" server: applicationConnectors: diff --git a/config.yml.example b/config.yml.example index 9076e429..5f6e096d 100644 --- a/config.yml.example +++ b/config.yml.example @@ -78,12 +78,10 @@ database: password: sa properties: charSet: UTF-8 - maxWaitForConnection: 1s - validationQuery: "/* CommaFeed Health Check */ SELECT 1" - minSize: 1 - maxSize: 50 - checkConnectionWhileIdle: true - maxConnectionAge: 30m + validationQuery: "/* CommaFeed Health Check */ SELECT 1" + minSize: 1 + maxSize: 50 + maxConnectionAge: 30m server: applicationConnectors: From 3386a71c5ec44e3be3450d1210ec53ad79ba971d Mon Sep 17 00:00:00 2001 From: Athou Date: Wed, 18 Feb 2015 12:03:28 +0100 Subject: [PATCH 145/241] smaller cleanup batches --- .../java/com/commafeed/backend/dao/FeedEntryDAO.java | 9 ++++++--- .../backend/service/DatabaseCleaningService.java | 10 +++++++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/commafeed/backend/dao/FeedEntryDAO.java b/src/main/java/com/commafeed/backend/dao/FeedEntryDAO.java index f58a4d62..c2ae7867 100644 --- a/src/main/java/com/commafeed/backend/dao/FeedEntryDAO.java +++ b/src/main/java/com/commafeed/backend/dao/FeedEntryDAO.java @@ -41,11 +41,14 @@ public class FeedEntryDAO extends GenericDAO { return tuples.stream().map(t -> new FeedCapacity(t.get(entry.feed.id), t.get(count))).collect(Collectors.toList()); } + public int delete(Long feedId, long max) { + List list = newQuery().from(entry).where(entry.feed.id.eq(feedId)).limit(max).list(entry); + return delete(list); + } + public int deleteOldEntries(Long feedId, long max) { List list = newQuery().from(entry).where(entry.feed.id.eq(feedId)).orderBy(entry.updated.asc()).limit(max).list(entry); - int deleted = list.size(); - delete(list); - return deleted; + return delete(list); } @AllArgsConstructor diff --git a/src/main/java/com/commafeed/backend/service/DatabaseCleaningService.java b/src/main/java/com/commafeed/backend/service/DatabaseCleaningService.java index 55805b59..48d0936f 100644 --- a/src/main/java/com/commafeed/backend/service/DatabaseCleaningService.java +++ b/src/main/java/com/commafeed/backend/service/DatabaseCleaningService.java @@ -17,6 +17,7 @@ import com.commafeed.backend.dao.FeedEntryDAO; import com.commafeed.backend.dao.FeedEntryDAO.FeedCapacity; import com.commafeed.backend.dao.FeedEntryStatusDAO; import com.commafeed.backend.dao.UnitOfWork; +import com.commafeed.backend.model.Feed; /** * Contains utility methods for cleaning the database @@ -40,7 +41,14 @@ public class DatabaseCleaningService { long total = 0; int deleted = 0; do { - deleted = UnitOfWork.run(sessionFactory, () -> feedDAO.delete(feedDAO.findWithoutSubscriptions(1))); + List feeds = UnitOfWork.run(sessionFactory, () -> feedDAO.findWithoutSubscriptions(1)); + for (Feed feed : feeds) { + int entriesDeleted = 0; + do { + entriesDeleted = UnitOfWork.run(sessionFactory, () -> feedEntryDAO.delete(feed.getId(), BATCH_SIZE)); + } while (entriesDeleted == BATCH_SIZE); + } + deleted = UnitOfWork.run(sessionFactory, () -> feedDAO.delete(feeds)); total += deleted; log.info("removed {} feeds without subscriptions", total); } while (deleted != 0); From 3b7689975d3479bdf7467894b0fdc5959b201b6d Mon Sep 17 00:00:00 2001 From: Athou Date: Wed, 18 Feb 2015 12:30:34 +0100 Subject: [PATCH 146/241] unit of work not needed here --- .../java/com/commafeed/frontend/servlet/CustomCssServlet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/commafeed/frontend/servlet/CustomCssServlet.java b/src/main/java/com/commafeed/frontend/servlet/CustomCssServlet.java index 2ee89d8f..22f51db9 100644 --- a/src/main/java/com/commafeed/frontend/servlet/CustomCssServlet.java +++ b/src/main/java/com/commafeed/frontend/servlet/CustomCssServlet.java @@ -32,7 +32,7 @@ public class CustomCssServlet extends HttpServlet { protected void doGet(final HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/css"); - final Optional user = UnitOfWork.run(sessionFactory, () -> new SessionHelper(req).getLoggedInUser()); + final Optional user = new SessionHelper(req).getLoggedInUser(); if (!user.isPresent()) { return; } From d1973922cd786f87cb81d3b0e1e38046a42e2aea Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 19 Feb 2015 08:30:44 +0100 Subject: [PATCH 147/241] check for empty lists too --- .../java/com/commafeed/backend/dao/FeedEntryStatusDAO.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java b/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java index 1843dc2c..b652bda9 100644 --- a/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java +++ b/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java @@ -8,6 +8,7 @@ import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.builder.CompareToBuilder; import org.hibernate.SessionFactory; @@ -118,7 +119,7 @@ public class FeedEntryStatusDAO extends GenericDAO { HibernateQuery query = newQuery().from(entry).where(entry.feed.eq(sub.getFeed())); - if (keywords != null) { + if (CollectionUtils.isNotEmpty(keywords)) { query.join(entry.content, content); for (FeedEntryKeyword keyword : keywords) { From 467d1a754d762be4c7419fb258281f9246cf5813 Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 19 Feb 2015 12:43:40 +0100 Subject: [PATCH 148/241] distinct not needed as we don't have duplicates in the id column --- src/main/java/com/commafeed/backend/dao/FeedEntryDAO.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/commafeed/backend/dao/FeedEntryDAO.java b/src/main/java/com/commafeed/backend/dao/FeedEntryDAO.java index c2ae7867..a5a13790 100644 --- a/src/main/java/com/commafeed/backend/dao/FeedEntryDAO.java +++ b/src/main/java/com/commafeed/backend/dao/FeedEntryDAO.java @@ -36,7 +36,7 @@ public class FeedEntryDAO extends GenericDAO { } public List findFeedsExceedingCapacity(long maxCapacity, long max) { - NumberExpression count = entry.id.countDistinct(); + NumberExpression count = entry.id.count(); List tuples = newQuery().from(entry).groupBy(entry.feed).having(count.gt(maxCapacity)).limit(max).list(entry.feed.id, count); return tuples.stream().map(t -> new FeedCapacity(t.get(entry.feed.id), t.get(count))).collect(Collectors.toList()); } From 191680a01bc4b10ed4c8db1314fd53321038422a Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 19 Feb 2015 13:00:47 +0100 Subject: [PATCH 149/241] correctly set timeout on query --- .../java/com/commafeed/backend/dao/FeedEntryStatusDAO.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java b/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java index b652bda9..524012dd 100644 --- a/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java +++ b/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java @@ -104,7 +104,11 @@ public class FeedEntryStatusDAO extends GenericDAO { query.orderBy(status.entryUpdated.desc(), status.id.desc()); } - query.offset(offset).limit(limit).setTimeout(config.getApplicationSettings().getQueryTimeout()); + query.offset(offset).limit(limit); + int timeout = config.getApplicationSettings().getQueryTimeout(); + if (timeout > 0) { + query.setTimeout(timeout / 1000); + } List statuses = query.list(status); for (FeedEntryStatus status : statuses) { From 812988b31a4994876b50862ea16296cfcdb016d4 Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 20 Feb 2015 08:51:33 +0100 Subject: [PATCH 150/241] log entries deleted --- .../commafeed/backend/service/DatabaseCleaningService.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/commafeed/backend/service/DatabaseCleaningService.java b/src/main/java/com/commafeed/backend/service/DatabaseCleaningService.java index 48d0936f..d13ec2b6 100644 --- a/src/main/java/com/commafeed/backend/service/DatabaseCleaningService.java +++ b/src/main/java/com/commafeed/backend/service/DatabaseCleaningService.java @@ -40,13 +40,16 @@ public class DatabaseCleaningService { log.info("cleaning feeds without subscriptions"); long total = 0; int deleted = 0; + long entriesTotal = 0; do { List feeds = UnitOfWork.run(sessionFactory, () -> feedDAO.findWithoutSubscriptions(1)); for (Feed feed : feeds) { int entriesDeleted = 0; do { entriesDeleted = UnitOfWork.run(sessionFactory, () -> feedEntryDAO.delete(feed.getId(), BATCH_SIZE)); - } while (entriesDeleted == BATCH_SIZE); + entriesTotal += entriesDeleted; + log.info("removed {} entries for feeds without subscriptions", entriesTotal); + } while (entriesDeleted > 0); } deleted = UnitOfWork.run(sessionFactory, () -> feedDAO.delete(feeds)); total += deleted; From 136c37885d18b42edf1b53bdf1b686bdc4a77f83 Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 20 Feb 2015 16:41:20 +0100 Subject: [PATCH 151/241] dropwizard upgrade --- pom.xml | 3 +-- src/main/java/com/commafeed/CommaFeedApplication.java | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index d520dda1..4ab37036 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ UTF-8 1.8 - 0.8.0-rc2 + 0.8.0-rc3 4.0-beta5 3.6.1 1.5.0 @@ -205,7 +205,6 @@ io.dropwizard dropwizard-forms ${dropwizard.version} - pom diff --git a/src/main/java/com/commafeed/CommaFeedApplication.java b/src/main/java/com/commafeed/CommaFeedApplication.java index 21957d70..b5d021ec 100644 --- a/src/main/java/com/commafeed/CommaFeedApplication.java +++ b/src/main/java/com/commafeed/CommaFeedApplication.java @@ -3,6 +3,7 @@ package com.commafeed; import io.dropwizard.Application; import io.dropwizard.assets.AssetsBundle; import io.dropwizard.db.DataSourceFactory; +import io.dropwizard.forms.MultiPartBundle; import io.dropwizard.hibernate.HibernateBundle; import io.dropwizard.migrations.MigrationsBundle; import io.dropwizard.server.DefaultServerFactory; @@ -24,7 +25,6 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import org.eclipse.jetty.server.session.SessionHandler; -import org.glassfish.jersey.media.multipart.MultiPartFeature; import org.hibernate.cfg.AvailableSettings; import com.commafeed.backend.feed.FeedRefreshTaskGiver; @@ -108,6 +108,7 @@ public class CommaFeedApplication extends Application { }); bootstrap.addBundle(new AssetsBundle("/assets/", "/", "index.html")); + bootstrap.addBundle(new MultiPartBundle()); } @Override @@ -134,9 +135,6 @@ public class CommaFeedApplication extends Application { environment.jersey().register(injector.getInstance(ServerREST.class)); environment.jersey().register(injector.getInstance(UserREST.class)); - // @FormDataParam support - environment.jersey().register(MultiPartFeature.class); - // Servlets environment.servlets().addServlet("next", injector.getInstance(NextUnreadServlet.class)).addMapping("/next"); environment.servlets().addServlet("logout", injector.getInstance(LogoutServlet.class)).addMapping("/logout"); From 88b98a138f5e63cc1c874ace21f720206673d212 Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 20 Feb 2015 17:06:33 +0100 Subject: [PATCH 152/241] swagger upgrade --- bower.json | 2 +- gulpfile.js | 1 + pom.xml | 12 ++------- src/main/app/api/index.html | 12 ++++----- .../com/commafeed/CommaFeedApplication.java | 27 ++++++++----------- 5 files changed, 20 insertions(+), 34 deletions(-) diff --git a/bower.json b/bower.json index fcbe71d9..ef04e00b 100644 --- a/bower.json +++ b/bower.json @@ -28,7 +28,7 @@ "devicejs": "0.1.16", "readabilicons": "arc90/readability-readabilicons#34c55561c5b8ec6e90714b50237c06b13cb9d59c", "zocial-less": "1.0.0", - "swagger-ui": "2.0.24" + "swagger-ui": "2.1.5-M1" }, "resolutions": { "angular": "1.3.6", diff --git a/gulpfile.js b/gulpfile.js index e2403641..cb78cf28 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -98,6 +98,7 @@ gulp.task('serve', function() { port : 8082, livereload : true, middleware : function() { + var api = '^/api/(.*)$ http://localhost:8083/rest/$1 [P]'; var rest = '^/rest/(.*)$ http://localhost:8083/rest/$1 [P]'; var next = '^/next(.*)$ http://localhost:8083/next$1 [P]'; var logout = '^/logout$ http://localhost:8083/logout [P]'; diff --git a/pom.xml b/pom.xml index 4ab37036..2b6efca9 100644 --- a/pom.xml +++ b/pom.xml @@ -209,21 +209,13 @@ com.wordnik - swagger-jaxrs_2.10 - 1.3.12 + swagger-jaxrs + 1.5.1-M1 javax.ws.rs jsr311-api - - com.fasterxml.jackson.module - jackson-module-scala_2.10 - - - org.scala-lang - scalap - diff --git a/src/main/app/api/index.html b/src/main/app/api/index.html index 05a25131..b78abbcf 100644 --- a/src/main/app/api/index.html +++ b/src/main/app/api/index.html @@ -2,7 +2,7 @@ Swagger UI - + @@ -12,25 +12,23 @@ - + - + + - + From 8b43af49fcc3e50227ee6c50d7051853e5c89bda Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 19 Mar 2015 11:13:34 +0100 Subject: [PATCH 167/241] enable batch inserts/updates --- src/main/java/com/commafeed/CommaFeedApplication.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/commafeed/CommaFeedApplication.java b/src/main/java/com/commafeed/CommaFeedApplication.java index 09c5c4e7..c7d651a2 100644 --- a/src/main/java/com/commafeed/CommaFeedApplication.java +++ b/src/main/java/com/commafeed/CommaFeedApplication.java @@ -90,6 +90,9 @@ public class CommaFeedApplication extends Application { // keep using old id generator for backward compatibility factory.getProperties().put(AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, "false"); + + factory.getProperties().put(AvailableSettings.STATEMENT_BATCH_SIZE, "50"); + factory.getProperties().put(AvailableSettings.BATCH_VERSIONED_DATA, "true"); return factory; } }); From 4278101bbefaba8f6e90e266dafcf9131af5a380 Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 19 Mar 2015 11:14:44 +0100 Subject: [PATCH 168/241] maven plugins update --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 81c2dd58..1644ee23 100644 --- a/pom.xml +++ b/pom.xml @@ -48,7 +48,7 @@ pl.project13.maven git-commit-id-plugin - 2.1.11 + 2.1.13 @@ -99,7 +99,7 @@ com.github.eirslett frontend-maven-plugin - 0.0.20 + 0.0.22 install node and npm @@ -140,7 +140,7 @@ org.apache.maven.plugins maven-jar-plugin - 2.5 + 2.6 From 0b2ada5d1cc6455f03203816c18f61b85de11647 Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 19 Mar 2015 11:22:56 +0100 Subject: [PATCH 169/241] depend directly on httpclient --- pom.xml | 11 ++++++----- src/main/java/com/commafeed/backend/HttpGetter.java | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 1644ee23..dffddf18 100644 --- a/pom.xml +++ b/pom.xml @@ -186,11 +186,6 @@ dropwizard-hibernate ${dropwizard.version} - - io.dropwizard - dropwizard-client - ${dropwizard.version} - io.dropwizard dropwizard-migrations @@ -207,6 +202,12 @@ ${dropwizard.version} + + org.apache.httpcomponents + httpclient + 4.4 + + com.wordnik swagger-jaxrs diff --git a/src/main/java/com/commafeed/backend/HttpGetter.java b/src/main/java/com/commafeed/backend/HttpGetter.java index 78921ae8..d26aec84 100644 --- a/src/main/java/com/commafeed/backend/HttpGetter.java +++ b/src/main/java/com/commafeed/backend/HttpGetter.java @@ -34,7 +34,7 @@ import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.config.ConnectionConfig; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; @@ -182,7 +182,7 @@ public class HttpGetter { builder.disableAutomaticRetries(); builder.setSslcontext(SSL_CONTEXT); - builder.setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); + builder.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE); RequestConfig.Builder configBuilder = RequestConfig.custom(); configBuilder.setCookieSpec(CookieSpecs.IGNORE_COOKIES); From 3966cf165ba973b7a29649071fb5321ab38c7745 Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 19 Mar 2015 12:32:42 +0100 Subject: [PATCH 170/241] log exceptions in trace level only --- .../commafeed/backend/favicon/DefaultFaviconFetcher.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/commafeed/backend/favicon/DefaultFaviconFetcher.java b/src/main/java/com/commafeed/backend/favicon/DefaultFaviconFetcher.java index b4e3da67..9645f793 100644 --- a/src/main/java/com/commafeed/backend/favicon/DefaultFaviconFetcher.java +++ b/src/main/java/com/commafeed/backend/favicon/DefaultFaviconFetcher.java @@ -67,7 +67,8 @@ public class DefaultFaviconFetcher extends AbstractFaviconFetcher { bytes = result.getContent(); contentType = result.getContentType(); } catch (Exception e) { - log.debug("Failed to retrieve iconAtRoot for url {}: ", url, e); + log.debug("Failed to retrieve iconAtRoot for url {}: ", url); + log.trace("Failed to retrieve iconAtRoot for url {}: ", url, e); } if (!isValidIconResponse(bytes, contentType)) { @@ -83,7 +84,8 @@ public class DefaultFaviconFetcher extends AbstractFaviconFetcher { HttpResult result = getter.getBinary(url, TIMEOUT); doc = Jsoup.parse(new String(result.getContent()), url); } catch (Exception e) { - log.debug("Failed to retrieve page to find icon", e); + log.debug("Failed to retrieve page to find icon"); + log.trace("Failed to retrieve page to find icon", e); return null; } @@ -109,7 +111,8 @@ public class DefaultFaviconFetcher extends AbstractFaviconFetcher { bytes = result.getContent(); contentType = result.getContentType(); } catch (Exception e) { - log.debug("Failed to retrieve icon found in page {}", href, e); + log.debug("Failed to retrieve icon found in page {}", href); + log.trace("Failed to retrieve icon found in page {}", href, e); return null; } From 6fd11fcd56667389ed6dfec3269ca9351e658aa9 Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 19 Mar 2015 12:35:38 +0100 Subject: [PATCH 171/241] don't load the feed, just update it --- src/main/java/com/commafeed/backend/dao/GenericDAO.java | 8 ++++++++ src/main/java/com/commafeed/backend/feed/FeedQueues.java | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/commafeed/backend/dao/GenericDAO.java b/src/main/java/com/commafeed/backend/dao/GenericDAO.java index f2217971..3a449a27 100644 --- a/src/main/java/com/commafeed/backend/dao/GenericDAO.java +++ b/src/main/java/com/commafeed/backend/dao/GenericDAO.java @@ -27,6 +27,14 @@ public abstract class GenericDAO extends AbstractDAO models.forEach(m -> persist(m)); } + public void update(T model) { + currentSession().update(model); + } + + public void update(Collection models) { + models.forEach(m -> update(m)); + } + public void merge(T model) { currentSession().merge(model); } diff --git a/src/main/java/com/commafeed/backend/feed/FeedQueues.java b/src/main/java/com/commafeed/backend/feed/FeedQueues.java index 88adccaa..8be30e2f 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedQueues.java +++ b/src/main/java/com/commafeed/backend/feed/FeedQueues.java @@ -136,7 +136,7 @@ public class FeedQueues { // update all feeds in the database List feeds = map.values().stream().filter(c -> config.getApplicationSettings().getHeavyLoad() ? !c.isUrgent() : true) .map(c -> c.getFeed()).collect(Collectors.toList()); - UnitOfWork.run(sessionFactory, () -> feedDAO.merge(feeds)); + UnitOfWork.run(sessionFactory, () -> feedDAO.update(feeds)); } /** From c7ab179a9ec17e82a0e2103280e8407e11739b40 Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 19 Mar 2015 13:01:03 +0100 Subject: [PATCH 172/241] cleanup --- .../com/commafeed/backend/dao/GenericDAO.java | 16 ---------------- .../com/commafeed/backend/feed/FeedQueues.java | 2 +- .../service/internal/PostLoginActivities.java | 2 +- .../commafeed/frontend/resource/UserREST.java | 2 +- 4 files changed, 3 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/commafeed/backend/dao/GenericDAO.java b/src/main/java/com/commafeed/backend/dao/GenericDAO.java index 3a449a27..db2a61ae 100644 --- a/src/main/java/com/commafeed/backend/dao/GenericDAO.java +++ b/src/main/java/com/commafeed/backend/dao/GenericDAO.java @@ -27,22 +27,6 @@ public abstract class GenericDAO extends AbstractDAO models.forEach(m -> persist(m)); } - public void update(T model) { - currentSession().update(model); - } - - public void update(Collection models) { - models.forEach(m -> update(m)); - } - - public void merge(T model) { - currentSession().merge(model); - } - - public void merge(Collection models) { - models.forEach(m -> merge(m)); - } - public T findById(Long id) { return get(id); } diff --git a/src/main/java/com/commafeed/backend/feed/FeedQueues.java b/src/main/java/com/commafeed/backend/feed/FeedQueues.java index 8be30e2f..8d81af3c 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedQueues.java +++ b/src/main/java/com/commafeed/backend/feed/FeedQueues.java @@ -136,7 +136,7 @@ public class FeedQueues { // update all feeds in the database List feeds = map.values().stream().filter(c -> config.getApplicationSettings().getHeavyLoad() ? !c.isUrgent() : true) .map(c -> c.getFeed()).collect(Collectors.toList()); - UnitOfWork.run(sessionFactory, () -> feedDAO.update(feeds)); + UnitOfWork.run(sessionFactory, () -> feedDAO.saveOrUpdate(feeds)); } /** diff --git a/src/main/java/com/commafeed/backend/service/internal/PostLoginActivities.java b/src/main/java/com/commafeed/backend/service/internal/PostLoginActivities.java index d27ab4b8..6bf7e071 100644 --- a/src/main/java/com/commafeed/backend/service/internal/PostLoginActivities.java +++ b/src/main/java/com/commafeed/backend/service/internal/PostLoginActivities.java @@ -39,7 +39,7 @@ public class PostLoginActivities { saveUser = true; } if (saveUser) { - userDAO.merge(user); + userDAO.saveOrUpdate(user); } } diff --git a/src/main/java/com/commafeed/frontend/resource/UserREST.java b/src/main/java/com/commafeed/frontend/resource/UserREST.java index ad4c54dc..58c85e52 100644 --- a/src/main/java/com/commafeed/frontend/resource/UserREST.java +++ b/src/main/java/com/commafeed/frontend/resource/UserREST.java @@ -215,7 +215,7 @@ public class UserREST { if (request.isNewApiKey()) { user.setApiKey(userService.generateApiKey(user)); } - userDAO.merge(user); + userDAO.saveOrUpdate(user); return Response.ok().build(); } From da3ce0748549b32f526d6739182f27010b293b91 Mon Sep 17 00:00:00 2001 From: Athou Date: Tue, 24 Mar 2015 16:40:20 +0100 Subject: [PATCH 173/241] fix documentation --- pom.xml | 2 +- src/main/java/com/commafeed/CommaFeedApplication.java | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index dffddf18..f859151a 100644 --- a/pom.xml +++ b/pom.xml @@ -211,7 +211,7 @@ com.wordnik swagger-jaxrs - 1.5.2-M1 + 1.5.3-M1 javax.ws.rs diff --git a/src/main/java/com/commafeed/CommaFeedApplication.java b/src/main/java/com/commafeed/CommaFeedApplication.java index c7d651a2..60ffa5ab 100644 --- a/src/main/java/com/commafeed/CommaFeedApplication.java +++ b/src/main/java/com/commafeed/CommaFeedApplication.java @@ -12,8 +12,10 @@ import io.dropwizard.setup.Bootstrap; import io.dropwizard.setup.Environment; import java.io.IOException; +import java.util.Arrays; import java.util.Date; import java.util.EnumSet; +import java.util.List; import java.util.Set; import java.util.concurrent.ScheduledExecutorService; @@ -24,6 +26,7 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; import org.eclipse.jetty.server.session.SessionHandler; import org.hibernate.cfg.AvailableSettings; @@ -159,11 +162,15 @@ public class CommaFeedApplication extends Application { environment.jersey().register(new ApiListingResource()); environment.getObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL); + String modelsPackage = "com.commafeed.frontend.model"; + String requestsPackage = "com.commafeed.frontend.model.request"; + String endpointsPackage = "com.commafeed.frontend.resource"; + List packages = Arrays.asList(modelsPackage, requestsPackage, endpointsPackage); BeanConfig swaggerConfig = new BeanConfig(); swaggerConfig.setTitle("CommaFeed"); swaggerConfig.setVersion("1"); swaggerConfig.setBasePath("/rest"); - swaggerConfig.setResourcePackage("com.commafeed.frontend.model"); + swaggerConfig.setResourcePackage(StringUtils.join(packages, ",")); swaggerConfig.setScan(true); // cache configuration From 74d4c18c4c174fcd83fea6cf75cc0bc7789efa1f Mon Sep 17 00:00:00 2001 From: Athou Date: Sun, 29 Mar 2015 21:28:36 +0200 Subject: [PATCH 174/241] keep only remove cascading --- src/main/java/com/commafeed/backend/model/User.java | 5 +---- src/main/java/com/commafeed/backend/service/UserService.java | 4 +++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/commafeed/backend/model/User.java b/src/main/java/com/commafeed/backend/model/User.java index e2d45449..aa2229ab 100644 --- a/src/main/java/com/commafeed/backend/model/User.java +++ b/src/main/java/com/commafeed/backend/model/User.java @@ -17,7 +17,6 @@ import lombok.Getter; import lombok.Setter; import org.apache.commons.lang3.time.DateUtils; -import org.hibernate.annotations.Cascade; import com.commafeed.backend.model.UserRole.Role; @@ -58,9 +57,7 @@ public class User extends AbstractModel { @Temporal(TemporalType.TIMESTAMP) private Date recoverPasswordTokenDate; - @OneToMany(mappedBy = "user", cascade = { CascadeType.PERSIST, CascadeType.REMOVE }) - @Cascade({ org.hibernate.annotations.CascadeType.PERSIST, org.hibernate.annotations.CascadeType.SAVE_UPDATE, - org.hibernate.annotations.CascadeType.REMOVE }) + @OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE) private Set roles = new HashSet<>(); @OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE) diff --git a/src/main/java/com/commafeed/backend/service/UserService.java b/src/main/java/com/commafeed/backend/service/UserService.java index b7172cfa..25112bd8 100644 --- a/src/main/java/com/commafeed/backend/service/UserService.java +++ b/src/main/java/com/commafeed/backend/service/UserService.java @@ -16,6 +16,7 @@ import org.apache.commons.lang3.StringUtils; import com.commafeed.CommaFeedConfiguration; import com.commafeed.backend.dao.FeedCategoryDAO; import com.commafeed.backend.dao.UserDAO; +import com.commafeed.backend.dao.UserRoleDAO; import com.commafeed.backend.dao.UserSettingsDAO; import com.commafeed.backend.model.User; import com.commafeed.backend.model.UserRole; @@ -29,6 +30,7 @@ public class UserService { private final FeedCategoryDAO feedCategoryDAO; private final UserDAO userDAO; + private final UserRoleDAO userRoleDAO; private final UserSettingsDAO userSettingsDAO; private final PasswordEncryptionService encryptionService; @@ -115,7 +117,7 @@ public class UserService { user.setSalt(salt); user.setPassword(encryptionService.getEncryptedPassword(password, salt)); for (Role role : roles) { - user.getRoles().add(new UserRole(user, role)); + userRoleDAO.saveOrUpdate(new UserRole(user, role)); } userDAO.saveOrUpdate(user); return user; From fb2add305e0ef3a3419795a502d0514fdb9c8340 Mon Sep 17 00:00:00 2001 From: Athou Date: Sun, 29 Mar 2015 21:34:47 +0200 Subject: [PATCH 175/241] fix build --- .../com/commafeed/backend/service/UserServiceTest.java | 7 +++++-- .../commafeed/frontend/auth/SecurityCheckFactoryTest.java | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/commafeed/backend/service/UserServiceTest.java b/src/test/java/com/commafeed/backend/service/UserServiceTest.java index e520dc46..f4088cc4 100644 --- a/src/test/java/com/commafeed/backend/service/UserServiceTest.java +++ b/src/test/java/com/commafeed/backend/service/UserServiceTest.java @@ -19,6 +19,7 @@ import org.mockito.MockitoAnnotations; import com.commafeed.CommaFeedConfiguration; import com.commafeed.backend.dao.FeedCategoryDAO; import com.commafeed.backend.dao.UserDAO; +import com.commafeed.backend.dao.UserRoleDAO; import com.commafeed.backend.dao.UserSettingsDAO; import com.commafeed.backend.model.User; import com.commafeed.backend.service.internal.PostLoginActivities; @@ -37,6 +38,8 @@ public class UserServiceTest { @Mock private UserSettingsDAO userSettingsDAO; @Mock + private UserRoleDAO userRoleDAO; + @Mock private PasswordEncryptionService passwordEncryptionService; @Mock private PostLoginActivities postLoginActivities; @@ -50,8 +53,8 @@ public class UserServiceTest { public void before_each_test() { MockitoAnnotations.initMocks(this); - userService = new UserService(feedCategoryDAO, userDAO, userSettingsDAO, passwordEncryptionService, commaFeedConfiguration, - postLoginActivities); + userService = new UserService(feedCategoryDAO, userDAO, userRoleDAO, userSettingsDAO, passwordEncryptionService, + commaFeedConfiguration, postLoginActivities); disabledUser = new User(); disabledUser.setDisabled(true); diff --git a/src/test/java/com/commafeed/frontend/auth/SecurityCheckFactoryTest.java b/src/test/java/com/commafeed/frontend/auth/SecurityCheckFactoryTest.java index 6f747dce..28c62005 100644 --- a/src/test/java/com/commafeed/frontend/auth/SecurityCheckFactoryTest.java +++ b/src/test/java/com/commafeed/frontend/auth/SecurityCheckFactoryTest.java @@ -24,7 +24,7 @@ public class SecurityCheckFactoryTest { PostLoginActivities postLoginActivities = mock(PostLoginActivities.class); - UserService service = new UserService(null, null, null, null, null, postLoginActivities); + UserService service = new UserService(null, null, null, null, null, null, postLoginActivities); SecurityCheckFactory factory = new SecurityCheckFactory(null, false); factory.userService = service; From 35e05677057b96bb35fd4caaff97fc73c01a3b20 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 30 Mar 2015 08:32:57 +0200 Subject: [PATCH 176/241] fix exception when saving role for a non-existing user --- src/main/java/com/commafeed/backend/service/UserService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/commafeed/backend/service/UserService.java b/src/main/java/com/commafeed/backend/service/UserService.java index 25112bd8..980a81a5 100644 --- a/src/main/java/com/commafeed/backend/service/UserService.java +++ b/src/main/java/com/commafeed/backend/service/UserService.java @@ -116,10 +116,10 @@ public class UserService { user.setCreated(new Date()); user.setSalt(salt); user.setPassword(encryptionService.getEncryptedPassword(password, salt)); + userDAO.saveOrUpdate(user); for (Role role : roles) { userRoleDAO.saveOrUpdate(new UserRole(user, role)); } - userDAO.saveOrUpdate(user); return user; } From cc1e173552fc438933659db935785e04eaad7c40 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 30 Mar 2015 09:43:44 +0200 Subject: [PATCH 177/241] remove role link from user --- src/main/java/com/commafeed/backend/dao/UserDAO.java | 10 +++------- src/main/java/com/commafeed/backend/model/User.java | 10 ---------- .../com/commafeed/backend/service/UserService.java | 5 +++++ .../commafeed/frontend/auth/SecurityCheckFactory.java | 4 +++- 4 files changed, 11 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/commafeed/backend/dao/UserDAO.java b/src/main/java/com/commafeed/backend/dao/UserDAO.java index 6159392f..3bc94cb2 100644 --- a/src/main/java/com/commafeed/backend/dao/UserDAO.java +++ b/src/main/java/com/commafeed/backend/dao/UserDAO.java @@ -6,7 +6,6 @@ import javax.inject.Singleton; import org.hibernate.SessionFactory; import com.commafeed.backend.model.QUser; -import com.commafeed.backend.model.QUserRole; import com.commafeed.backend.model.User; @Singleton @@ -20,18 +19,15 @@ public class UserDAO extends GenericDAO { } public User findByName(String name) { - return newQuery().from(user).where(user.name.equalsIgnoreCase(name)).leftJoin(user.roles, QUserRole.userRole).fetch() - .uniqueResult(user); + return newQuery().from(user).where(user.name.equalsIgnoreCase(name)).uniqueResult(user); } public User findByApiKey(String key) { - return newQuery().from(user).where(user.apiKey.equalsIgnoreCase(key)).leftJoin(user.roles, QUserRole.userRole).fetch() - .uniqueResult(user); + return newQuery().from(user).where(user.apiKey.equalsIgnoreCase(key)).uniqueResult(user); } public User findByEmail(String email) { - return newQuery().from(user).where(user.email.equalsIgnoreCase(email)).leftJoin(user.roles, QUserRole.userRole).fetch() - .uniqueResult(user); + return newQuery().from(user).where(user.email.equalsIgnoreCase(email)).uniqueResult(user); } public long count() { diff --git a/src/main/java/com/commafeed/backend/model/User.java b/src/main/java/com/commafeed/backend/model/User.java index aa2229ab..b97d6c7d 100644 --- a/src/main/java/com/commafeed/backend/model/User.java +++ b/src/main/java/com/commafeed/backend/model/User.java @@ -1,7 +1,6 @@ package com.commafeed.backend.model; import java.util.Date; -import java.util.HashSet; import java.util.Set; import javax.persistence.CascadeType; @@ -18,8 +17,6 @@ import lombok.Setter; import org.apache.commons.lang3.time.DateUtils; -import com.commafeed.backend.model.UserRole.Role; - @Entity @Table(name = "USERS") @SuppressWarnings("serial") @@ -57,9 +54,6 @@ public class User extends AbstractModel { @Temporal(TemporalType.TIMESTAMP) private Date recoverPasswordTokenDate; - @OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE) - private Set roles = new HashSet<>(); - @OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE) private Set subscriptions; @@ -67,10 +61,6 @@ public class User extends AbstractModel { @Temporal(TemporalType.TIMESTAMP) private Date lastFullRefresh; - public boolean hasRole(Role role) { - return getRoles().stream().anyMatch(r -> r.getRole() == role); - } - public boolean shouldRefreshFeedsAt(Date when) { return (lastFullRefresh == null || lastFullRefreshMoreThan30MinutesBefore(when)); } diff --git a/src/main/java/com/commafeed/backend/service/UserService.java b/src/main/java/com/commafeed/backend/service/UserService.java index 980a81a5..66692189 100644 --- a/src/main/java/com/commafeed/backend/service/UserService.java +++ b/src/main/java/com/commafeed/backend/service/UserService.java @@ -3,6 +3,7 @@ package com.commafeed.backend.service; import java.util.Collection; import java.util.Date; import java.util.Optional; +import java.util.Set; import java.util.UUID; import javax.inject.Inject; @@ -133,4 +134,8 @@ public class UserService { byte[] key = encryptionService.getEncryptedPassword(UUID.randomUUID().toString(), user.getSalt()); return DigestUtils.sha1Hex(key); } + + public Set getRoles(User user) { + return userRoleDAO.findRoles(user); + } } diff --git a/src/main/java/com/commafeed/frontend/auth/SecurityCheckFactory.java b/src/main/java/com/commafeed/frontend/auth/SecurityCheckFactory.java index 673790f8..469a2e9c 100644 --- a/src/main/java/com/commafeed/frontend/auth/SecurityCheckFactory.java +++ b/src/main/java/com/commafeed/frontend/auth/SecurityCheckFactory.java @@ -1,6 +1,7 @@ package com.commafeed.frontend.auth; import java.util.Optional; +import java.util.Set; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; @@ -46,7 +47,8 @@ public class SecurityCheckFactory extends AbstractContainerRequestValueFactory roles = userService.getRoles(user.get()); + if (roles.contains(role)) { return user.get(); } else { throw new WebApplicationException(Response.status(Response.Status.FORBIDDEN) From c872b335e7a4afaf65877c967841ec6355bb9966 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 30 Mar 2015 10:14:40 +0200 Subject: [PATCH 178/241] correctly remove user and all its dependencies --- src/main/java/com/commafeed/backend/model/User.java | 7 ------- .../java/com/commafeed/backend/service/UserService.java | 4 ++++ .../com/commafeed/backend/service/UserServiceTest.java | 7 +++++-- .../commafeed/frontend/auth/SecurityCheckFactoryTest.java | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/commafeed/backend/model/User.java b/src/main/java/com/commafeed/backend/model/User.java index b97d6c7d..93963a3d 100644 --- a/src/main/java/com/commafeed/backend/model/User.java +++ b/src/main/java/com/commafeed/backend/model/User.java @@ -1,13 +1,9 @@ package com.commafeed.backend.model; import java.util.Date; -import java.util.Set; -import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; @@ -54,9 +50,6 @@ public class User extends AbstractModel { @Temporal(TemporalType.TIMESTAMP) private Date recoverPasswordTokenDate; - @OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE) - private Set subscriptions; - @Column(name = "last_full_refresh") @Temporal(TemporalType.TIMESTAMP) private Date lastFullRefresh; diff --git a/src/main/java/com/commafeed/backend/service/UserService.java b/src/main/java/com/commafeed/backend/service/UserService.java index 66692189..c5601a2f 100644 --- a/src/main/java/com/commafeed/backend/service/UserService.java +++ b/src/main/java/com/commafeed/backend/service/UserService.java @@ -16,6 +16,7 @@ import org.apache.commons.lang3.StringUtils; import com.commafeed.CommaFeedConfiguration; import com.commafeed.backend.dao.FeedCategoryDAO; +import com.commafeed.backend.dao.FeedSubscriptionDAO; import com.commafeed.backend.dao.UserDAO; import com.commafeed.backend.dao.UserRoleDAO; import com.commafeed.backend.dao.UserSettingsDAO; @@ -30,6 +31,7 @@ import com.google.common.base.Preconditions; public class UserService { private final FeedCategoryDAO feedCategoryDAO; + private final FeedSubscriptionDAO feedSubscriptionDAO; private final UserDAO userDAO; private final UserRoleDAO userRoleDAO; private final UserSettingsDAO userSettingsDAO; @@ -127,6 +129,8 @@ public class UserService { public void unregister(User user) { feedCategoryDAO.delete(feedCategoryDAO.findAll(user)); userSettingsDAO.delete(userSettingsDAO.findByUser(user)); + userRoleDAO.delete(userRoleDAO.findAll(user)); + feedSubscriptionDAO.delete(feedSubscriptionDAO.findAll(user)); userDAO.delete(user); } diff --git a/src/test/java/com/commafeed/backend/service/UserServiceTest.java b/src/test/java/com/commafeed/backend/service/UserServiceTest.java index f4088cc4..9e132df1 100644 --- a/src/test/java/com/commafeed/backend/service/UserServiceTest.java +++ b/src/test/java/com/commafeed/backend/service/UserServiceTest.java @@ -18,6 +18,7 @@ import org.mockito.MockitoAnnotations; import com.commafeed.CommaFeedConfiguration; import com.commafeed.backend.dao.FeedCategoryDAO; +import com.commafeed.backend.dao.FeedSubscriptionDAO; import com.commafeed.backend.dao.UserDAO; import com.commafeed.backend.dao.UserRoleDAO; import com.commafeed.backend.dao.UserSettingsDAO; @@ -34,6 +35,8 @@ public class UserServiceTest { @Mock private FeedCategoryDAO feedCategoryDAO; @Mock + private FeedSubscriptionDAO feedSubscriptionDAO; + @Mock private UserDAO userDAO; @Mock private UserSettingsDAO userSettingsDAO; @@ -53,8 +56,8 @@ public class UserServiceTest { public void before_each_test() { MockitoAnnotations.initMocks(this); - userService = new UserService(feedCategoryDAO, userDAO, userRoleDAO, userSettingsDAO, passwordEncryptionService, - commaFeedConfiguration, postLoginActivities); + userService = new UserService(feedCategoryDAO, feedSubscriptionDAO, userDAO, userRoleDAO, userSettingsDAO, + passwordEncryptionService, commaFeedConfiguration, postLoginActivities); disabledUser = new User(); disabledUser.setDisabled(true); diff --git a/src/test/java/com/commafeed/frontend/auth/SecurityCheckFactoryTest.java b/src/test/java/com/commafeed/frontend/auth/SecurityCheckFactoryTest.java index 28c62005..75bfc513 100644 --- a/src/test/java/com/commafeed/frontend/auth/SecurityCheckFactoryTest.java +++ b/src/test/java/com/commafeed/frontend/auth/SecurityCheckFactoryTest.java @@ -24,7 +24,7 @@ public class SecurityCheckFactoryTest { PostLoginActivities postLoginActivities = mock(PostLoginActivities.class); - UserService service = new UserService(null, null, null, null, null, null, postLoginActivities); + UserService service = new UserService(null, null, null, null, null, null, null, postLoginActivities); SecurityCheckFactory factory = new SecurityCheckFactory(null, false); factory.userService = service; From 3e77a83ca661e32eba4310470f929811e7b88949 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 30 Mar 2015 10:55:42 +0200 Subject: [PATCH 179/241] unnecessary optimization --- src/main/java/com/commafeed/backend/feed/FeedQueues.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/commafeed/backend/feed/FeedQueues.java b/src/main/java/com/commafeed/backend/feed/FeedQueues.java index 8d81af3c..15d9bf7d 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedQueues.java +++ b/src/main/java/com/commafeed/backend/feed/FeedQueues.java @@ -134,8 +134,7 @@ public class FeedQueues { } // update all feeds in the database - List feeds = map.values().stream().filter(c -> config.getApplicationSettings().getHeavyLoad() ? !c.isUrgent() : true) - .map(c -> c.getFeed()).collect(Collectors.toList()); + List feeds = map.values().stream().map(c -> c.getFeed()).collect(Collectors.toList()); UnitOfWork.run(sessionFactory, () -> feedDAO.saveOrUpdate(feeds)); } From cebeef04a0d15f31a95ffbf253fea13b239ec737 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 30 Mar 2015 11:31:58 +0200 Subject: [PATCH 180/241] remove one to many relationships --- src/main/java/com/commafeed/backend/dao/FeedDAO.java | 10 +++++++--- src/main/java/com/commafeed/backend/model/Feed.java | 9 --------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/commafeed/backend/dao/FeedDAO.java b/src/main/java/com/commafeed/backend/dao/FeedDAO.java index bd1ebeb8..9a4e2795 100644 --- a/src/main/java/com/commafeed/backend/dao/FeedDAO.java +++ b/src/main/java/com/commafeed/backend/dao/FeedDAO.java @@ -17,6 +17,7 @@ import com.commafeed.backend.model.QUser; import com.google.common.collect.Iterables; import com.mysema.query.BooleanBuilder; import com.mysema.query.jpa.hibernate.HibernateQuery; +import com.mysema.query.jpa.hibernate.HibernateSubQuery; @Singleton public class FeedDAO extends GenericDAO { @@ -33,12 +34,14 @@ public class FeedDAO extends GenericDAO { disabledDatePredicate.or(feed.disabledUntil.isNull()); disabledDatePredicate.or(feed.disabledUntil.lt(new Date())); - HibernateQuery query = newQuery().from(feed); + HibernateQuery query = null; if (lastLoginThreshold != null) { QFeedSubscription subs = QFeedSubscription.feedSubscription; QUser user = QUser.user; - query.join(feed.subscriptions, subs).join(subs.user, user).where(disabledDatePredicate, user.lastLogin.gt(lastLoginThreshold)); + query = newQuery().from(subs); + query.join(subs.feed, feed).join(subs.user, user).where(disabledDatePredicate, user.lastLogin.gt(lastLoginThreshold)); } else { + query = newQuery().from(feed); query.where(disabledDatePredicate); } @@ -60,6 +63,7 @@ public class FeedDAO extends GenericDAO { public List findWithoutSubscriptions(int max) { QFeedSubscription sub = QFeedSubscription.feedSubscription; - return newQuery().from(feed).leftJoin(feed.subscriptions, sub).where(sub.id.isNull()).limit(max).list(feed); + return newQuery().from(feed).where(new HibernateSubQuery().from(sub).where(sub.feed.eq(feed)).notExists()).limit(max).list(feed); + // return newQuery().from(feed).leftJoin(feed.subscriptions, sub).where(sub.id.isNull()).limit(max).list(feed); } } diff --git a/src/main/java/com/commafeed/backend/model/Feed.java b/src/main/java/com/commafeed/backend/model/Feed.java index 8c15a670..9ac4e736 100644 --- a/src/main/java/com/commafeed/backend/model/Feed.java +++ b/src/main/java/com/commafeed/backend/model/Feed.java @@ -1,12 +1,9 @@ package com.commafeed.backend.model; import java.util.Date; -import java.util.Set; -import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; -import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; @@ -103,12 +100,6 @@ public class Feed extends AbstractModel { @Column(length = 40) private String lastContentHash; - @OneToMany(mappedBy = "feed", cascade = CascadeType.REMOVE) - private Set entries; - - @OneToMany(mappedBy = "feed") - private Set subscriptions; - /** * detected hub for pubsubhubbub */ From f5d0eb94b403e1a6a9cf0bb23b5fb382621765c5 Mon Sep 17 00:00:00 2001 From: Athou Date: Wed, 1 Apr 2015 19:45:54 +0200 Subject: [PATCH 181/241] verious upgrades --- pom.xml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pom.xml b/pom.xml index f859151a..ed200b9f 100644 --- a/pom.xml +++ b/pom.xml @@ -9,15 +9,15 @@ CommaFeed - 3.0.5 + 3.1.0 UTF-8 1.8 - 0.8.0 + 0.8.1-rc2 4.0-beta5 - 3.6.2 + 3.6.3 1.5.0 @@ -39,7 +39,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 + 3.3 ${java.version} ${java.version} @@ -99,7 +99,7 @@ com.github.eirslett frontend-maven-plugin - 0.0.22 + 0.0.23 install node and npm @@ -162,7 +162,7 @@ org.slf4j slf4j-api - 1.7.10 + 1.7.12 @@ -205,7 +205,7 @@ org.apache.httpcomponents httpclient - 4.4 + 4.4.1 @@ -273,7 +273,7 @@ redis.clients jedis - 2.6.2 + 2.7.0 com.sun.mail @@ -317,7 +317,7 @@ net.sourceforge.cssparser cssparser - 0.9.14 + 0.9.15 @@ -328,7 +328,7 @@ mysql mysql-connector-java - 5.1.34 + 5.1.35 org.postgresql From c48ea1152cff5649c6237d8aeba1969a996f9b69 Mon Sep 17 00:00:00 2001 From: Athou Date: Wed, 1 Apr 2015 22:05:39 +0200 Subject: [PATCH 182/241] fix jenkins build --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ed200b9f..172a06d7 100644 --- a/pom.xml +++ b/pom.xml @@ -39,7 +39,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.3 + 3.1 ${java.version} ${java.version} From c7f211a7f8493af279af9ddd0fe447925ec598be Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 3 Apr 2015 12:45:28 +0200 Subject: [PATCH 183/241] ubuntu LTS has maven 3.0.5 and this upgrade does not add much --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 172a06d7..5e06ce45 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ CommaFeed - 3.1.0 + 3.0.5 @@ -99,7 +99,7 @@ com.github.eirslett frontend-maven-plugin - 0.0.23 + 0.0.22 install node and npm From 6901b9b7288173cef63478ffb981ab5646d24447 Mon Sep 17 00:00:00 2001 From: JmsBnz Date: Tue, 7 Apr 2015 18:13:23 +0200 Subject: [PATCH 184/241] Update it.js Some adjustment to the italian translation, not complete yet. --- src/main/app/i18n/it.js | 80 ++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/src/main/app/i18n/it.js b/src/main/app/i18n/it.js index c11303a2..2221f8b2 100644 --- a/src/main/app/i18n/it.js +++ b/src/main/app/i18n/it.js @@ -3,7 +3,7 @@ "save" : "Salva", "cancel" : "Cancella", "delete" : "Elimina", - "required" : "Required", + "required" : "Richiesto", "download" : "Download", "link" : "Link", "bookmark" : "Segnalibro ", @@ -19,7 +19,7 @@ }, "subscribe" : { "feed_url" : "Feed URL", - "feed_name" : "Nome Feed", + "feed_name" : "Nome feed", "category" : "Categoria" }, "import" : { @@ -39,12 +39,12 @@ "previous_entry" : "Precedente", "next_entry" : "Successivo", "refresh" : "Ricarica", - "refresh_all" : "Force refresh all my feeds ", - "sort_by_asc_desc" : "Sort by date asc/desc", + "refresh_all" : "Forza l'aggiornamento di tutte le fonti", + "sort_by_asc_desc" : "Ordina per data crescente/decrescente", "titles_only" : "Solo titoli", "expanded_view" : "Espandi", "mark_all_as_read" : "Contrassegna tutto come già letto", - "mark_all_older_12_hours" : "Items older than 12 hours ", + "mark_all_older_12_hours" : "Elementi più vecchi di 12 ore", "mark_all_older_day" : "Elementi più vecchi di un giorno", "mark_all_older_week" : "Elementi più vecchi di una settimana", "mark_all_older_two_weeks" : "Elementi più vecchi di due settimane", @@ -58,7 +58,7 @@ "view" : { "entry_source" : "from ", "entry_author" : "by ", - "error_while_loading_feed" : "Si è verificato un errore, mentre caricavo il feed", + "error_while_loading_feed" : "Si è verificato un errore mentre caricavo il feed", "keep_unread" : "Mantiene come da leggere", "no_unread_items" : "Non ci sono elementi da leggere.", "mark_up_to_here" : "Mark as read up to here ", @@ -67,7 +67,7 @@ }, "feedsearch" : { "hint" : "Type in a subscription... ", - "help" : "Use the return key to select and arrow keys to navigate. ", + "help" : "Usa il tasto Invio per selezionare e le frecce per navigare.", "result_prefix" : "Le tue sottoscrizioni" }, "settings" : { @@ -75,51 +75,51 @@ "value" : "Generali", "language" : "Lingua", "language_contribute" : "Contribuisci con le traduzioni", - "show_unread" : "Show feeds and categories with no unread entries", + "show_unread" : "Mostra le fonti e le categorie con gli elementi già letti", "social_buttons" : "Visualizza i social button", "scroll_marks" : "Marca come letto quando scorri" }, - "appearance" : "Appearance ", - "scroll_speed" : "Scrolling speed when navigating between entries (in milliseconds) ", - "scroll_speed_help" : "set to 0 to disable ", + "appearance" : "Aspetto ", + "scroll_speed" : "Velocità dello scorrimento durante la navigazione fra i feed (in millisecondi) ", + "scroll_speed_help" : "Imposta a 0 per disabilitare ", "theme" : "Tema ", "submit_your_theme" : "Sottoponi il tuo tema ", - "custom_css" : "Css modificato" + "custom_css" : "CSS modificato " }, "details" : { - "feed_details" : "Dettagli feed", - "url" : "URL", - "website" : "Website ", - "name" : "Nome", - "category" : "Categoria", + "feed_details" : "Dettagli feed ", + "url" : "URL ", + "website" : "Sito Web ", + "name" : "Nome ", + "category" : "Categoria ", "position" : "Posizione ", - "last_refresh" : "Ultimo aggiornamento", + "last_refresh" : "Ultimo aggiornamento ", "message" : "Last refresh message ", "next_refresh" : "Next refresh ", "queued_for_refresh" : "In attesa per l'aggiornamento ", - "feed_url" : "Feed URL", + "feed_url" : "URL del feed ", "generate_api_key_first" : "Generate an API key in your profile first.", - "unsubscribe" : "Annulla l\"'\"iscrizione", - "unsubscribe_confirmation" : "Are you sure you want to unsubscribe from this feed? ", - "delete_category_confirmation" : "Are you sure you want to delete this category? ", + "unsubscribe" : "Annulla l\"'\"iscrizione ", + "unsubscribe_confirmation" : "Si è certi di voler annullare la sottoscrizione da questo feed? ", + "delete_category_confirmation" : "Si è certi di voler eliminare questa categoria? ", "category_details" : "Dettagli categoria", "tag_details" : "Tag details ", "parent_category" : "Parent category" }, "profile" : { - "user_name" : "User name", + "user_name" : "User name ", "email" : "E-mail", - "change_password" : "Cambia password", - "confirm_password" : "Conferma password", - "minimum_6_chars" : "Minimo 6 caratteri", + "change_password" : "Cambia password ", + "confirm_password" : "Conferma password ", + "minimum_6_chars" : "Minimo 6 caratteri ", "passwords_do_not_match" : "Le password non corrispondono", - "api_key" : "API key", - "api_key_not_generated" : "Non generata ancora", - "generate_new_api_key" : "Genera una nuova chiave API", - "generate_new_api_key_info" : "Cambiando la password sarà generata una nuova chiave API", - "opml_export" : "Esporta OPML", - "delete_account" : "Elimina account", - "delete_account_confirmation" : "Delete your acount? There's no turning back! " + "api_key" : "API key ", + "api_key_not_generated" : "Non gancora generata", + "generate_new_api_key" : "Genera una nuova chiave API ", + "generate_new_api_key_info" : "Cambiando la password sarà generata una nuova chiave API ", + "opml_export" : "Esporta OPML ", + "delete_account" : "Elimina account ", + "delete_account_confirmation" : "Eliminare l'account? Non si può tornare indietro! " }, "about" : { "rest_api" : { @@ -127,9 +127,9 @@ "line1" : "CommaFeed is built on top of JAX-RS and AngularJS. As such, a REST API is available.", "link_to_documentation" : "Link alla documentazione." }, - "keyboard_shortcuts" : "Scorciatoie da tastiera", - "version" : "CommaFeed version ", - "line1_prefix" : "Commefeed è un progetto open-source. I codici sono hostati su ", + "keyboard_shortcuts" : "Scorciatoie da tastiera", + "version" : "Versione di CommaFeed", + "line1_prefix" : "CommaFeed è un progetto open source. I codici sono ospitati su ", "line1_suffix" : ".", "line2_prefix" : "Se hai qualche problema, segnalalo sulla pagina del ", "line2_suffix" : " progetto.", @@ -139,12 +139,12 @@ "value" : "Goodies", "android_app" : "Android app ", "subscribe_url" : "Subscribe URL ", - "chrome_extension" : "Estenzione per Chrome ", + "chrome_extension" : "Estensione per Chrome", "firefox_extension" : "Estensione per Firefox", "opera_extension" : "Estensione per Opera", "subscribe_bookmarklet" : "Add subscription bookmarklet (click) ", - "subscribe_bookmarklet_asc" : "Oldest first ", - "subscribe_bookmarklet_desc" : "Newest first ", + "subscribe_bookmarklet_asc" : "I più vecchi prima", + "subscribe_bookmarklet_desc" : "I più nuovi prima", "next_unread_bookmarklet" : "Next unread item bookmarklet (drag to bookmark bar) " }, "translation" : { @@ -177,4 +177,4 @@ "feed_search" : "navigate to a subscription by entering the subscription name " } } -} \ No newline at end of file +} From 6b6ff70ad33e1cfd9936d73b9d1faf8e49ff10da Mon Sep 17 00:00:00 2001 From: Athou Date: Wed, 8 Apr 2015 15:14:41 +0200 Subject: [PATCH 185/241] dropwizard release --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 5e06ce45..5b2c8b1d 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ UTF-8 1.8 - 0.8.1-rc2 + 0.8.1 4.0-beta5 3.6.3 1.5.0 @@ -312,7 +312,7 @@ com.ibm.icu icu4j - 54.1.1 + 55.1 net.sourceforge.cssparser From 600d05d08fe8f0e32678eb43db787e0009e3c837 Mon Sep 17 00:00:00 2001 From: LelixSuper Date: Wed, 15 Apr 2015 16:19:34 +0200 Subject: [PATCH 186/241] Update it.js Partial update of the translation of the Italian language. I council calls only Italian to translate, because the Italian language is complex enough. --- src/main/app/i18n/it.js | 46 ++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/main/app/i18n/it.js b/src/main/app/i18n/it.js index 2221f8b2..9b5d05e2 100644 --- a/src/main/app/i18n/it.js +++ b/src/main/app/i18n/it.js @@ -8,7 +8,7 @@ "link" : "Link", "bookmark" : "Segnalibro ", "close" : "Chiudi", - "tags" : "Tags " + "tags" : "Targhette " }, "tree" : { "subscribe" : "Iscriviti", @@ -31,7 +31,7 @@ }, "new_category" : { "name" : "Nome", - "parent" : "Parent" + "parent" : "Gruppo" }, "toolbar" : { "unread" : "Non letti", @@ -52,18 +52,18 @@ "profile" : "Profilo", "admin" : "Admin", "about" : "Informazioni", - "logout" : "Logout", + "logout" : "Esci", "donate" : "Dona" }, "view" : { - "entry_source" : "from ", - "entry_author" : "by ", + "entry_source" : "da ", + "entry_author" : "di ", "error_while_loading_feed" : "Si è verificato un errore mentre caricavo il feed", "keep_unread" : "Mantiene come da leggere", "no_unread_items" : "Non ci sono elementi da leggere.", - "mark_up_to_here" : "Mark as read up to here ", - "search_for" : "searching for: ", - "no_search_results" : "No match found for the requested keywords " + "mark_up_to_here" : "Contrassegna come già letto fino qui", + "search_for" : "Cerca: ", + "no_search_results" : "Nessun risultato trovato per le parole chiave richieste " }, "feedsearch" : { "hint" : "Type in a subscription... ", @@ -76,15 +76,15 @@ "language" : "Lingua", "language_contribute" : "Contribuisci con le traduzioni", "show_unread" : "Mostra le fonti e le categorie con gli elementi già letti", - "social_buttons" : "Visualizza i social button", + "social_buttons" : "Visualizza i pulsanti per i social network", "scroll_marks" : "Marca come letto quando scorri" }, - "appearance" : "Aspetto ", + "appearance" : "Aspetto", "scroll_speed" : "Velocità dello scorrimento durante la navigazione fra i feed (in millisecondi) ", "scroll_speed_help" : "Imposta a 0 per disabilitare ", "theme" : "Tema ", - "submit_your_theme" : "Sottoponi il tuo tema ", - "custom_css" : "CSS modificato " + "submit_your_theme" : "Proponi il tuo tema ", + "custom_css" : "CSS personalizzato " }, "details" : { "feed_details" : "Dettagli feed ", @@ -94,20 +94,20 @@ "category" : "Categoria ", "position" : "Posizione ", "last_refresh" : "Ultimo aggiornamento ", - "message" : "Last refresh message ", - "next_refresh" : "Next refresh ", + "message" : "Ultimo messaggio di aggiornamento ", + "next_refresh" : "Prossimo aggiornamento ", "queued_for_refresh" : "In attesa per l'aggiornamento ", "feed_url" : "URL del feed ", - "generate_api_key_first" : "Generate an API key in your profile first.", - "unsubscribe" : "Annulla l\"'\"iscrizione ", + "generate_api_key_first" : "Genera prima una chiave API nelle impostazioni del tuo profilo.", + "unsubscribe" : "Annulla l'iscrizione ", "unsubscribe_confirmation" : "Si è certi di voler annullare la sottoscrizione da questo feed? ", "delete_category_confirmation" : "Si è certi di voler eliminare questa categoria? ", "category_details" : "Dettagli categoria", - "tag_details" : "Tag details ", - "parent_category" : "Parent category" + "tag_details" : "TDettagli targhette ", + "parent_category" : "Categoria principale" }, "profile" : { - "user_name" : "User name ", + "user_name" : "Nome utente ", "email" : "E-mail", "change_password" : "Cambia password ", "confirm_password" : "Conferma password ", @@ -134,11 +134,11 @@ "line2_prefix" : "Se hai qualche problema, segnalalo sulla pagina del ", "line2_suffix" : " progetto.", "line3" : "Se ti piace il progetto, prendi in considerazione una donazione per supportare lo sviluppatore e contribuire a coprire i costi di mantenenimento di questo sito on-line.", - "line4" : "Per chi preferisce Bitcoin, questo è l\"'\"indirizzo ", + "line4" : "Se preferisci i Bitcoin, questo è l'indirizzo ", "goodies" : { "value" : "Goodies", - "android_app" : "Android app ", - "subscribe_url" : "Subscribe URL ", + "android_app" : "Applicazione Android ", + "subscribe_url" : "Sottoscrivi URL ", "chrome_extension" : "Estensione per Chrome", "firefox_extension" : "Estensione per Firefox", "opera_extension" : "Estensione per Opera", @@ -150,7 +150,7 @@ "translation" : { "value" : "Traduzioni", "message" : "Abbiamo bisogno del tuo aiuto per tradurre CommaFeed.", - "link" : "Vedi come aiutare con le traduzioni." + "link" : "Vedi come aiutarrci con le traduzioni." }, "announcements" : "Annunci", "shortcuts" : { From fc104b0b014e1b75177dab61cc44f6657c471047 Mon Sep 17 00:00:00 2001 From: Athou Date: Wed, 22 Apr 2015 13:04:16 +0200 Subject: [PATCH 187/241] fix eclipse infinite build loop --- pom.xml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/pom.xml b/pom.xml index 5b2c8b1d..20b40b69 100644 --- a/pom.xml +++ b/pom.xml @@ -35,6 +35,39 @@ true + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + com.github.eirslett + frontend-maven-plugin + [0.0.22,) + + npm + gulp + bower + + + + + false + + + + + + + + + org.apache.maven.plugins From 4d71a8f3c2bca0ed51a1b628f8b07d56d032c720 Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 23 Apr 2015 08:55:47 +0200 Subject: [PATCH 188/241] rewrite query using not exists --- .../com/commafeed/backend/dao/FeedEntryContentDAO.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/commafeed/backend/dao/FeedEntryContentDAO.java b/src/main/java/com/commafeed/backend/dao/FeedEntryContentDAO.java index b0239132..5056e7f5 100644 --- a/src/main/java/com/commafeed/backend/dao/FeedEntryContentDAO.java +++ b/src/main/java/com/commafeed/backend/dao/FeedEntryContentDAO.java @@ -11,6 +11,7 @@ import com.commafeed.backend.model.FeedEntryContent; import com.commafeed.backend.model.QFeedEntry; import com.commafeed.backend.model.QFeedEntryContent; import com.google.common.collect.Iterables; +import com.mysema.query.jpa.hibernate.HibernateSubQuery; @Singleton public class FeedEntryContentDAO extends GenericDAO { @@ -30,8 +31,10 @@ public class FeedEntryContentDAO extends GenericDAO { public int deleteWithoutEntries(int max) { QFeedEntry entry = QFeedEntry.feedEntry; - List list = newQuery().from(content).leftJoin(content.entries, entry).where(entry.id.isNull()).limit(max) - .list(content); + + HibernateSubQuery subQuery = new HibernateSubQuery().from(entry).where(entry.content.id.eq(content.id)); + List list = newQuery().from(content).where(subQuery.notExists()).limit(max).list(content); + int deleted = list.size(); delete(list); return deleted; From fee3e10e6b9cdad98d34c872cdb645072f4ae968 Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 24 Apr 2015 16:14:18 +0200 Subject: [PATCH 189/241] return a more explicit error message (fix #723) --- src/main/java/com/commafeed/frontend/resource/FeedREST.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/commafeed/frontend/resource/FeedREST.java b/src/main/java/com/commafeed/frontend/resource/FeedREST.java index a16fe27a..aa2fc04a 100644 --- a/src/main/java/com/commafeed/frontend/resource/FeedREST.java +++ b/src/main/java/com/commafeed/frontend/resource/FeedREST.java @@ -436,8 +436,7 @@ public class FeedREST { try { feedEntryFilteringService.filterMatchesEntry(req.getFilter(), TEST_ENTRY); } catch (FeedEntryFilterException e) { - Throwable root = Throwables.getRootCause(e); - return Response.status(Status.BAD_REQUEST).entity(root.getMessage()).type(MediaType.TEXT_PLAIN).build(); + return Response.status(Status.BAD_REQUEST).entity(e.getCause().getMessage()).type(MediaType.TEXT_PLAIN).build(); } FeedSubscription subscription = feedSubscriptionDAO.findById(user, req.getId()); From a216444825612cfd6d1123f6be3517f3d6b7a60c Mon Sep 17 00:00:00 2001 From: Athou Date: Wed, 29 Apr 2015 12:45:05 +0200 Subject: [PATCH 190/241] upgrade dependencies --- pom.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 20b40b69..5f5e6df6 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ UTF-8 1.8 0.8.1 - 4.0-beta5 + 4.0 3.6.3 1.5.0 @@ -189,7 +189,7 @@ org.projectlombok lombok - 1.16.2 + 1.16.4 provided @@ -289,7 +289,7 @@ org.apache.commons commons-math3 - 3.4.1 + 3.5 org.apache.commons @@ -306,12 +306,12 @@ redis.clients jedis - 2.7.0 + 2.7.2 com.sun.mail javax.mail - 1.5.2 + 1.5.3 @@ -340,7 +340,7 @@ org.jsoup jsoup - 1.8.1 + 1.8.2 com.ibm.icu @@ -356,7 +356,7 @@ com.h2database h2 - 1.4.186 + 1.4.187 mysql @@ -383,7 +383,7 @@ org.mockito mockito-core - 2.0.5-beta + 2.0.7-beta test From e8769d09a81fe812b66cd5d7fc700c4b52464765 Mon Sep 17 00:00:00 2001 From: Athou Date: Sun, 3 May 2015 09:08:49 +0200 Subject: [PATCH 191/241] update readme (fix #724) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e849deba..7705562d 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Browser extensions: [Chrome](https://github.com/Athou/commafeed-chrome) - [Firef mvn clean package cp config.yml.example config.yml vi config.yml - java -jar target/commafeed.jar server config.yml + java -Djava.net.preferIPv4Stack=true -jar target/commafeed.jar server config.yml ### The long version @@ -51,7 +51,7 @@ Now build the application Copy `config.yml.example` to `config.yml` then edit the file to your liking. Issue the following command to run the app, the server will listen by default on `http://localhost:8082`. The default user is `admin` and the default password is `admin`. - java -jar target/commafeed.jar server config.yml + java -Djava.net.preferIPv4Stack=true -jar target/commafeed.jar server config.yml You can use a proxy http server such as nginx or apache. From efa38d5ee99736d528f0581226f1c9200aa6ae02 Mon Sep 17 00:00:00 2001 From: Athou Date: Sun, 3 May 2015 09:17:54 +0200 Subject: [PATCH 192/241] store and expose entry categories (#727) --- src/main/app/i18n/en.js | 2 +- src/main/app/templates/feeds.view.html | 3 +++ .../java/com/commafeed/backend/feed/FeedParser.java | 2 ++ .../com/commafeed/backend/model/FeedEntryContent.java | 3 +++ .../backend/service/FeedEntryFilteringService.java | 1 + src/main/java/com/commafeed/frontend/model/Entry.java | 4 ++++ src/main/resources/changelogs/db.changelog-2.2.xml | 11 +++++++++++ src/main/resources/migrations.xml | 1 + 8 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/changelogs/db.changelog-2.2.xml diff --git a/src/main/app/i18n/en.js b/src/main/app/i18n/en.js index d267ec54..f6fa7c8b 100644 --- a/src/main/app/i18n/en.js +++ b/src/main/app/i18n/en.js @@ -99,7 +99,7 @@ "queued_for_refresh" : "Queued for refresh", "feed_url" : "Feed URL", "filtering_expression" : "Filtering expression", - "filtering_expression_help" : "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically.\nAvailable variables are 'title', 'content', 'url' and 'author' and their content is converted to lower case to ease string comparison.\nExample: url.contains('youtube') or (author eq 'athou' and title.contains('github').\nComplete available syntax is available here.", + "filtering_expression_help" : "If not empty, an expression evaluating to 'true' or 'false'. If false, new entries for this feed will be marked as read automatically.\nAvailable variables are 'title', 'content', 'url' 'author' and 'categories' and their content is converted to lower case to ease string comparison.\nExample: url.contains('youtube') or (author eq 'athou' and title.contains('github').\nComplete available syntax is available here.", "generate_api_key_first" : "Generate an API key in your profile first.", "unsubscribe" : "Unsubscribe", "unsubscribe_confirmation" : "Are you sure you want to unsubscribe from this feed?", diff --git a/src/main/app/templates/feeds.view.html b/src/main/app/templates/feeds.view.html index 9d29c0a6..9b748444 100644 --- a/src/main/app/templates/feeds.view.html +++ b/src/main/app/templates/feeds.view.html @@ -51,6 +51,9 @@ + + ({{entry.categories}}) + diff --git a/src/main/java/com/commafeed/backend/feed/FeedParser.java b/src/main/java/com/commafeed/backend/feed/FeedParser.java index ac49b254..4b483af4 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedParser.java +++ b/src/main/java/com/commafeed/backend/feed/FeedParser.java @@ -86,6 +86,8 @@ public class FeedParser { FeedEntryContent content = new FeedEntryContent(); content.setContent(getContent(item)); + content.setCategories(FeedUtils.truncate( + item.getCategories().stream().map(c -> c.getName()).collect(Collectors.joining(", ")), 4096)); content.setTitle(getTitle(item)); content.setAuthor(StringUtils.trimToNull(item.getAuthor())); SyndEnclosure enclosure = Iterables.getFirst(item.getEnclosures(), null); diff --git a/src/main/java/com/commafeed/backend/model/FeedEntryContent.java b/src/main/java/com/commafeed/backend/model/FeedEntryContent.java index fe34967b..7b036d20 100644 --- a/src/main/java/com/commafeed/backend/model/FeedEntryContent.java +++ b/src/main/java/com/commafeed/backend/model/FeedEntryContent.java @@ -43,6 +43,9 @@ public class FeedEntryContent extends AbstractModel { @Column(length = 255) private String enclosureType; + @Column(length = 4096) + private String categories; + @OneToMany(mappedBy = "content") private Set entries; diff --git a/src/main/java/com/commafeed/backend/service/FeedEntryFilteringService.java b/src/main/java/com/commafeed/backend/service/FeedEntryFilteringService.java index bfb7d4ff..c576ff45 100644 --- a/src/main/java/com/commafeed/backend/service/FeedEntryFilteringService.java +++ b/src/main/java/com/commafeed/backend/service/FeedEntryFilteringService.java @@ -89,6 +89,7 @@ public class FeedEntryFilteringService { context.set("content", entry.getContent().getContent() == null ? "" : Jsoup.parse(entry.getContent().getContent()).text() .toLowerCase()); context.set("url", entry.getUrl() == null ? "" : entry.getUrl().toLowerCase()); + context.set("categories", entry.getContent().getCategories() == null ? "" : entry.getContent().getCategories().toLowerCase()); Callable callable = script.callable(context); Future future = executor.submit(callable); diff --git a/src/main/java/com/commafeed/frontend/model/Entry.java b/src/main/java/com/commafeed/frontend/model/Entry.java index abdd9530..b709a278 100644 --- a/src/main/java/com/commafeed/frontend/model/Entry.java +++ b/src/main/java/com/commafeed/frontend/model/Entry.java @@ -56,6 +56,7 @@ public class Entry implements Serializable { entry.setAuthor(content.getAuthor()); entry.setEnclosureUrl(content.getEnclosureUrl()); entry.setEnclosureType(content.getEnclosureType()); + entry.setCategories(content.getCategories()); } return entry; @@ -95,6 +96,9 @@ public class Entry implements Serializable { @ApiModelProperty("entry content") private String content; + @ApiModelProperty("comma-separated list of categories") + private String categories; + @ApiModelProperty("wether entry content and title are rtl") private boolean rtl; diff --git a/src/main/resources/changelogs/db.changelog-2.2.xml b/src/main/resources/changelogs/db.changelog-2.2.xml new file mode 100644 index 00000000..1af324a6 --- /dev/null +++ b/src/main/resources/changelogs/db.changelog-2.2.xml @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/src/main/resources/migrations.xml b/src/main/resources/migrations.xml index 8da3a73d..05b0e223 100644 --- a/src/main/resources/migrations.xml +++ b/src/main/resources/migrations.xml @@ -10,5 +10,6 @@ + \ No newline at end of file From ab201d501672e9245105d3233af5a42e54df60e4 Mon Sep 17 00:00:00 2001 From: ebraminio Date: Sun, 17 May 2015 01:16:05 +0430 Subject: [PATCH 193/241] Make manifest.json accessible https://commafeed.com/manifest.json is not accessible currently --- gulpfile.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index cb78cf28..f086f8bd 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -26,7 +26,8 @@ gulp.task('favicons', function() { var favicons_png = SRC_DIR + '*.png'; var favicons_ico = SRC_DIR + '*.ico'; var favicons_svg = SRC_DIR + '*.svg'; - return gulp.src([favicons_png, favicons_ico, favicons_svg]).pipe(gulp.dest(BUILD_DIR)); + var manifest = SRC_DIR + 'manifest.json'; + return gulp.src([favicons_png, favicons_ico, favicons_svg, manifest]).pipe(gulp.dest(BUILD_DIR)); }); gulp.task('sass', function() { @@ -110,4 +111,4 @@ gulp.task('serve', function() { }); gulp.task('dev', ['build-dev', 'watch', 'serve']); -gulp.task('default', ['build']); \ No newline at end of file +gulp.task('default', ['build']); From c6321fc6b23ad740532ca5273a8b23a8d6d33e0b Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 21 May 2015 11:56:52 +0200 Subject: [PATCH 194/241] rename gulp task --- gulpfile.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index f086f8bd..d9d3f736 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -22,7 +22,7 @@ gulp.task('i18n', function() { return gulp.src(SRC_DIR + 'i18n/**/*.js').pipe(gulp.dest(BUILD_DIR + 'i18n')); }); -gulp.task('favicons', function() { +gulp.task('resources', function() { var favicons_png = SRC_DIR + '*.png'; var favicons_ico = SRC_DIR + '*.ico'; var favicons_svg = SRC_DIR + '*.svg'; @@ -61,7 +61,7 @@ gulp.task('template-cache', function() { return gulp.src(SRC_DIR + 'templates/**/*.html').pipe(templateCache(options)).pipe(gulp.dest(TEMP_DIR + 'js')); }); -gulp.task('build-dev', ['images', 'i18n', 'favicons', 'sass', 'fonts', 'select2', 'swagger-ui', 'template-cache'], function() { +gulp.task('build-dev', ['images', 'i18n', 'resources', 'sass', 'fonts', 'select2', 'swagger-ui', 'template-cache'], function() { var assets = useref.assets({ searchPath : [SRC_DIR, TEMP_DIR] }); @@ -71,7 +71,7 @@ gulp.task('build-dev', ['images', 'i18n', 'favicons', 'sass', 'fonts', 'select2' revReplace()).pipe(gulp.dest(BUILD_DIR)).pipe(connect.reload()); }); -gulp.task('build', ['images', 'i18n', 'favicons', 'sass', 'fonts', 'select2', 'swagger-ui', 'template-cache'], function() { +gulp.task('build', ['images', 'i18n', 'resources', 'sass', 'fonts', 'select2', 'swagger-ui', 'template-cache'], function() { var assets = useref.assets({ searchPath : [SRC_DIR, TEMP_DIR] }); From 7521013e11fbec2aa94a48a060bebbc29993da0a Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 21 May 2015 11:58:48 +0200 Subject: [PATCH 195/241] fix openshift start script (#731) --- .openshift/action_hooks/start | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.openshift/action_hooks/start b/.openshift/action_hooks/start index 53ce9a5c..a1562fc5 100755 --- a/.openshift/action_hooks/start +++ b/.openshift/action_hooks/start @@ -1,3 +1,4 @@ #!/bin/bash cd $OPENSHIFT_REPO_DIR -nohup java -jar target/commafeed.jar server .openshift/config.mysql.yml > ${OPENSHIFT_DIY_LOG_DIR}/commafeed.log 2>&1 & +export JAVA_HOME=$OPENSHIFT_DATA_DIR/jdk1.8.0_20 +nohup $JAVA_HOME/bin/java -jar target/commafeed.jar server .openshift/config.mysql.yml > ${OPENSHIFT_DIY_LOG_DIR}/commafeed.log 2>&1 & From a144fb2e48439107d716840dfcebd003a2cc1d42 Mon Sep 17 00:00:00 2001 From: LelixSuper Date: Sat, 23 May 2015 12:33:31 +0200 Subject: [PATCH 196/241] Update it.js Translated all the strings in Italian language --- src/main/app/i18n/it.js | 162 ++++++++++++++++++++-------------------- 1 file changed, 82 insertions(+), 80 deletions(-) diff --git a/src/main/app/i18n/it.js b/src/main/app/i18n/it.js index 9b5d05e2..f2760c91 100644 --- a/src/main/app/i18n/it.js +++ b/src/main/app/i18n/it.js @@ -6,12 +6,12 @@ "required" : "Richiesto", "download" : "Download", "link" : "Link", - "bookmark" : "Segnalibro ", + "bookmark" : "Segnalibro", "close" : "Chiudi", - "tags" : "Targhette " + "tags" : "Etichette " }, "tree" : { - "subscribe" : "Iscriviti", + "subscribe" : "Abbonati", "import" : "Importa", "new_category" : "Nuova categoria", "all" : "Tutto", @@ -23,10 +23,10 @@ "category" : "Categoria" }, "import" : { - "google_reader_prefix" : "Importa i tuoi feed dal tuo ", + "google_reader_prefix" : "Permettimi di importare i tuoi feed dal tuo ", "google_reader_suffix" : " account.", - "google_download" : "Oppure, carica il tuo subscriptions.xml", - "google_download_link" : "Scaricalo da qui", + "google_download" : "Oppure, carica il tuo file subscriptions.xml.", + "google_download_link" : "Scaricalo da qui.", "xml_file" : "OPML File" }, "new_category" : { @@ -39,11 +39,11 @@ "previous_entry" : "Precedente", "next_entry" : "Successivo", "refresh" : "Ricarica", - "refresh_all" : "Forza l'aggiornamento di tutte le fonti", - "sort_by_asc_desc" : "Ordina per data crescente/decrescente", - "titles_only" : "Solo titoli", + "refresh_all" : "Forza l'aggiornamento di tutte i miei feed", + "sort_by_asc_desc" : "Ordina per data ascendente/decrescente", + "titles_only" : "Solo i titoli", "expanded_view" : "Espandi", - "mark_all_as_read" : "Contrassegna tutto come già letto", + "mark_all_as_read" : "Segna tutto come già letto", "mark_all_older_12_hours" : "Elementi più vecchi di 12 ore", "mark_all_older_day" : "Elementi più vecchi di un giorno", "mark_all_older_week" : "Elementi più vecchi di una settimana", @@ -58,74 +58,76 @@ "view" : { "entry_source" : "da ", "entry_author" : "di ", - "error_while_loading_feed" : "Si è verificato un errore mentre caricavo il feed", - "keep_unread" : "Mantiene come da leggere", + "error_while_loading_feed" : "Si è verificato un errore durante il caricamento di questo feed", + "keep_unread" : "Mantiene come non leggere", "no_unread_items" : "Non ci sono elementi da leggere.", - "mark_up_to_here" : "Contrassegna come già letto fino qui", - "search_for" : "Cerca: ", - "no_search_results" : "Nessun risultato trovato per le parole chiave richieste " + "mark_up_to_here" : "Segna come letto fino qui", + "search_for" : "cercando: ", + "no_search_results" : "Nessun risultato trovato per le parole chiave cercate" }, "feedsearch" : { - "hint" : "Type in a subscription... ", - "help" : "Usa il tasto Invio per selezionare e le frecce per navigare.", - "result_prefix" : "Le tue sottoscrizioni" + "hint" : "Digita in una sottoscrizione... ", + "help" : "Usa il tasto invio per selezionare e le frecce per navigare.", + "result_prefix" : "Le tue sottoscrizioni:" }, "settings" : { "general" : { "value" : "Generali", "language" : "Lingua", - "language_contribute" : "Contribuisci con le traduzioni", - "show_unread" : "Mostra le fonti e le categorie con gli elementi già letti", - "social_buttons" : "Visualizza i pulsanti per i social network", - "scroll_marks" : "Marca come letto quando scorri" + "language_contribute" : "Contribuisci nelle traduzioni", + "show_unread" : "Mostra i feed e le categorie con elementi non letti", + "social_buttons" : "Mostra i pulsanti social network di condivisione", + "scroll_marks" : "In modalità estesa, segna come letto le voci quando scorri" }, "appearance" : "Aspetto", - "scroll_speed" : "Velocità dello scorrimento durante la navigazione fra i feed (in millisecondi) ", - "scroll_speed_help" : "Imposta a 0 per disabilitare ", - "theme" : "Tema ", - "submit_your_theme" : "Proponi il tuo tema ", - "custom_css" : "CSS personalizzato " + "scroll_speed" : "Velocità dello scorrimento durante la navigazione tra i feed (in millisecondi) ", + "scroll_speed_help" : "Imposta 0 per disabilitare", + "theme" : "Tema", + "submit_your_theme" : "Proponi il tuo tema", + "custom_css" : "CSS personalizzato" }, "details" : { - "feed_details" : "Dettagli feed ", + "feed_details" : "Dettagli feed", "url" : "URL ", - "website" : "Sito Web ", - "name" : "Nome ", - "category" : "Categoria ", - "position" : "Posizione ", - "last_refresh" : "Ultimo aggiornamento ", - "message" : "Ultimo messaggio di aggiornamento ", - "next_refresh" : "Prossimo aggiornamento ", - "queued_for_refresh" : "In attesa per l'aggiornamento ", + "website" : "Sito Web", + "name" : "Nome", + "category" : "Categoria", + "position" : "Posizione", + "last_refresh" : "Ultimo aggiornamento", + "message" : "Ultimo messaggio di aggiornamento", + "next_refresh" : "Prossimo aggiornamento", + "queued_for_refresh" : "In attesa per l'aggiornamento", "feed_url" : "URL del feed ", + "filtering_expression" : "Espressione del filtro", + "filtering_expression_help" : "Se non è vuoto, una espressione viene misurata in 'true' o 'false'. Se falsa, i nuovi elementi di questo feed verranno segnati automaticamente come letti.\nLe variabili accettate sono 'title', 'content', 'url' 'author' e 'categories' e il loro contenuto è convertito in minuscolo per una facile confronto di stringhe.\Esempio: url.contains('youtube') o (author eq 'athou' and title.contains('github').\nLa sintassi completa è disponibile qui.", "generate_api_key_first" : "Genera prima una chiave API nelle impostazioni del tuo profilo.", - "unsubscribe" : "Annulla l'iscrizione ", - "unsubscribe_confirmation" : "Si è certi di voler annullare la sottoscrizione da questo feed? ", - "delete_category_confirmation" : "Si è certi di voler eliminare questa categoria? ", + "unsubscribe" : "Annulla la sottoscrizione", + "unsubscribe_confirmation" : "Sei sicuro di voler annullare la sottoscrizione da questo feed?", + "delete_category_confirmation" : "Sei sicuro di voler eliminare questa categoria?", "category_details" : "Dettagli categoria", - "tag_details" : "TDettagli targhette ", + "tag_details" : "Dettagli etichette ", "parent_category" : "Categoria principale" }, "profile" : { - "user_name" : "Nome utente ", + "user_name" : "Nome utente", "email" : "E-mail", - "change_password" : "Cambia password ", - "confirm_password" : "Conferma password ", - "minimum_6_chars" : "Minimo 6 caratteri ", + "change_password" : "Cambia password", + "confirm_password" : "Conferma password", + "minimum_6_chars" : "Minimo 6 caratteri", "passwords_do_not_match" : "Le password non corrispondono", - "api_key" : "API key ", - "api_key_not_generated" : "Non gancora generata", + "api_key" : "chiave API", + "api_key_not_generated" : "Non ancora generata", "generate_new_api_key" : "Genera una nuova chiave API ", - "generate_new_api_key_info" : "Cambiando la password sarà generata una nuova chiave API ", - "opml_export" : "Esporta OPML ", - "delete_account" : "Elimina account ", - "delete_account_confirmation" : "Eliminare l'account? Non si può tornare indietro! " + "generate_new_api_key_info" : "Cambiando la password sarà generata una nuova chiave API ì", + "opml_export" : "Esporta OPML", + "delete_account" : "Elimina il profilo", + "delete_account_confirmation" : "Eliminare il tuo profilo? Non si può tornare indietro!" }, "about" : { "rest_api" : { "value" : "REST API", - "line1" : "CommaFeed is built on top of JAX-RS and AngularJS. As such, a REST API is available.", - "link_to_documentation" : "Link alla documentazione." + "line1" : "CommaFeed è costruito sopra JAX-RS e AngularJS. Ed ovviamente, una REST API è disponibile.", + "link_to_documentation" : "Collegamento alla documentazione." }, "keyboard_shortcuts" : "Scorciatoie da tastiera", "version" : "Versione di CommaFeed", @@ -133,48 +135,48 @@ "line1_suffix" : ".", "line2_prefix" : "Se hai qualche problema, segnalalo sulla pagina del ", "line2_suffix" : " progetto.", - "line3" : "Se ti piace il progetto, prendi in considerazione una donazione per supportare lo sviluppatore e contribuire a coprire i costi di mantenenimento di questo sito on-line.", - "line4" : "Se preferisci i Bitcoin, questo è l'indirizzo ", + "line3" : "Se ti piace il progetto, considera una donazione per supportare lo sviluppatore ed a aiutare per coprire i costi di mantenenimento di questo sito online.", + "line4" : "Se preferisci i Bitcoin, questo è l'indirizzo", "goodies" : { "value" : "Goodies", - "android_app" : "Applicazione Android ", - "subscribe_url" : "Sottoscrivi URL ", + "android_app" : "Applicazione Android", + "subscribe_url" : "Sottoscrivi URL", "chrome_extension" : "Estensione per Chrome", "firefox_extension" : "Estensione per Firefox", "opera_extension" : "Estensione per Opera", - "subscribe_bookmarklet" : "Add subscription bookmarklet (click) ", + "subscribe_bookmarklet" : "Aggiungi la sottoscrizione ai segnalibri (clicca)", "subscribe_bookmarklet_asc" : "I più vecchi prima", "subscribe_bookmarklet_desc" : "I più nuovi prima", - "next_unread_bookmarklet" : "Next unread item bookmarklet (drag to bookmark bar) " + "next_unread_bookmarklet" : "Prossimo elemento non letto nei segnalibri (trascinali nella barra dei segnalibri)" }, "translation" : { "value" : "Traduzioni", "message" : "Abbiamo bisogno del tuo aiuto per tradurre CommaFeed.", - "link" : "Vedi come aiutarrci con le traduzioni." + "link" : "Vedi come aiutarci nella traduzioni." }, "announcements" : "Annunci", "shortcuts" : { - "mouse_middleclick" : "mouse middleclick", - "open_next_entry" : "open next entry", - "open_previous_entry" : "open previous entry", - "spacebar" : "space/shift+space ", - "move_page_down_up" : "moves the page down/up ", - "focus_next_entry" : "set focus on next entry without opening it ", - "focus_previous_entry" : "set focus on previous entry without opening it ", - "open_next_feed" : "open next feed or category ", - "open_previous_feed" : "open previous feed or category ", - "open_close_current_entry" : "open/close current entry", - "open_current_entry_in_new_window" : "open current entry in a new window", - "open_current_entry_in_new_window_background" : "open current entry in a new window in the background ", - "star_unstar" : "star/unstar current entry", - "mark_current_entry" : "mark as read/unread current entry", - "mark_all_as_read" : "mark all entries as read", - "open_in_new_tab_mark_as_read" : "open entry in new tab and mark as read", - "fullscreen" : "toggle full screen mode ", - "font_size" : "increase/decrease font size of the current entry ", - "go_to_all" : "go to the All view ", - "go_to_starred" : "go to the Starred view ", - "feed_search" : "navigate to a subscription by entering the subscription name " + "mouse_middleclick" : "click centrale del mouse", + "open_next_entry" : "apri l'elemento successivo", + "open_previous_entry" : "apri l'elemento precedente", + "spacebar" : "spazio/shift+spazio", + "move_page_down_up" : "muovi la pagina sopra/sotto", + "focus_next_entry" : "imposta il fuoco sull'elemento successivo senza aprirlo", + "focus_previous_entry" : "imposta il fuoco sull'elemento precedente senza aprirlo", + "open_next_feed" : "apri il feed successivo od una categoria", + "open_previous_feed" : "apri il feed precedente od una categoria", + "open_close_current_entry" : "apri/chiusi la categoria corrente", + "open_current_entry_in_new_window" : "apri il corrente elemento in una nuova finestra", + "open_current_entry_in_new_window_background" : "apri il corrente elemento in una nuova finestra in secondo piano", + "star_unstar" : "segna/togli il segno all'elemento corrente", + "mark_current_entry" : "segna come letto/non letto l'elemento corrente", + "mark_all_as_read" : "segna come letti tutti gli elementi", + "open_in_new_tab_mark_as_read" : "apri l'elemento in una nuova finestra e segnala come letta", + "fullscreen" : "alterna la modalità a schermo intero", + "font_size" : "aumenta/decrementa la grandezza del font dell'elemento corrente", + "go_to_all" : "vai nella visione totale", + "go_to_starred" : "vai nella visione dei preferiti", + "feed_search" : "naviga in una sottoscrizione scrivendo il suo nome" } } } From e7d995edbc303aefa813da4976bdfe805459d43b Mon Sep 17 00:00:00 2001 From: Athou Date: Wed, 27 May 2015 09:48:28 +0200 Subject: [PATCH 197/241] node/npm upgrade --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 5f5e6df6..a28e5ff6 100644 --- a/pom.xml +++ b/pom.xml @@ -141,8 +141,8 @@ generate-resources - v0.10.35 - 2.2.0 + v0.12.4 + 2.10.1 From 748bfa31ae9ec9781ac6564e6d99fc99bd153561 Mon Sep 17 00:00:00 2001 From: Athou Date: Wed, 27 May 2015 09:51:46 +0200 Subject: [PATCH 198/241] build dependencies upgrade --- package.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index f96ab54c..02a22012 100644 --- a/package.json +++ b/package.json @@ -4,17 +4,17 @@ "main": "main.js", "private": true, "devDependencies": { - "bower": "1.3.12", + "bower": "1.4.1", "gulp": "3.8.11", - "gulp-rev": "3.0.1", - "gulp-rev-replace": "0.4.0", - "gulp-minify-css": "0.5.1", - "gulp-uglify": "1.1.0", + "gulp-rev": "4.0.0", + "gulp-rev-replace": "0.4.1", + "gulp-minify-css": "1.1.1", + "gulp-uglify": "1.2.0", "gulp-filter": "2.0.2", "gulp-connect": "2.2.0", - "connect-modrewrite": "0.7.11", - "gulp-sass": "1.3.3", - "gulp-useref": "1.1.1", - "gulp-angular-templatecache": "1.5.0" + "connect-modrewrite": "0.8.1", + "gulp-sass": "2.0.1", + "gulp-useref": "1.1.2", + "gulp-angular-templatecache": "1.6.0" } } From 7d75153362498c57deb6056cb09afdda257f3143 Mon Sep 17 00:00:00 2001 From: Athou Date: Wed, 27 May 2015 10:14:43 +0200 Subject: [PATCH 199/241] instructions on how to update an existing openshift installation --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 7705562d..c7d95b59 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,10 @@ You can use a proxy http server such as nginx or apache. git remote add upstream -m master https://github.com/Athou/commafeed.git git pull -s recursive -X theirs upstream master git push + + # To upgrade an existing openshift installation + git pull upstream master + git push ## Translate CommaFeed into your language From b0bfb73952e03ccea1b41e952e4c79f6d8ecf2a9 Mon Sep 17 00:00:00 2001 From: Athou Date: Sun, 31 May 2015 09:53:07 +0200 Subject: [PATCH 200/241] fix #734 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 02a22012..6de889da 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "gulp": "3.8.11", "gulp-rev": "4.0.0", "gulp-rev-replace": "0.4.1", - "gulp-minify-css": "1.1.1", + "gulp-minify-css": "1.1.0", "gulp-uglify": "1.2.0", "gulp-filter": "2.0.2", "gulp-connect": "2.2.0", From 4a52bd0cb736d8a41a7fa4a1c3f26a801e14c6f0 Mon Sep 17 00:00:00 2001 From: RavenB Date: Sun, 31 May 2015 20:35:43 +0200 Subject: [PATCH 201/241] fix for #734 and allowing version bump of minicss --- gulpfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index d9d3f736..e1902a67 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -79,7 +79,7 @@ gulp.task('build', ['images', 'i18n', 'resources', 'sass', 'fonts', 'select2', ' var cssFilter = filter("**/*.css"); return gulp.src([SRC_DIR + 'index.html', TEMP_DIR + 'app.css']).pipe(assets) - .pipe(cssFilter).pipe(minifyCSS()).pipe(cssFilter.restore()) + .pipe(cssFilter).pipe(minifyCSS({rebase: false})).pipe(cssFilter.restore()) .pipe(jsFilter).pipe(uglify()).pipe(jsFilter.restore()) From a0b5a1462d671faabd847f0beb06424e890e5d1f Mon Sep 17 00:00:00 2001 From: RavenB Date: Sun, 31 May 2015 20:39:32 +0200 Subject: [PATCH 202/241] version bump to latest minicss in gulpfile was corrected gulp-minify can now be safely upgraded. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6de889da..6a075410 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "gulp": "3.8.11", "gulp-rev": "4.0.0", "gulp-rev-replace": "0.4.1", - "gulp-minify-css": "1.1.0", + "gulp-minify-css": "1.1.3", "gulp-uglify": "1.2.0", "gulp-filter": "2.0.2", "gulp-connect": "2.2.0", From 74eaf48ceb1bbaa77e3948e6bb5fb1d9de3b4d79 Mon Sep 17 00:00:00 2001 From: Athou Date: Tue, 2 Jun 2015 08:45:16 +0200 Subject: [PATCH 203/241] upgrade gulp-minify-css and remove workaround (#734) --- gulpfile.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index e1902a67..d9d3f736 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -79,7 +79,7 @@ gulp.task('build', ['images', 'i18n', 'resources', 'sass', 'fonts', 'select2', ' var cssFilter = filter("**/*.css"); return gulp.src([SRC_DIR + 'index.html', TEMP_DIR + 'app.css']).pipe(assets) - .pipe(cssFilter).pipe(minifyCSS({rebase: false})).pipe(cssFilter.restore()) + .pipe(cssFilter).pipe(minifyCSS()).pipe(cssFilter.restore()) .pipe(jsFilter).pipe(uglify()).pipe(jsFilter.restore()) diff --git a/package.json b/package.json index 6a075410..f8c093d0 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "gulp": "3.8.11", "gulp-rev": "4.0.0", "gulp-rev-replace": "0.4.1", - "gulp-minify-css": "1.1.3", + "gulp-minify-css": "1.1.4", "gulp-uglify": "1.2.0", "gulp-filter": "2.0.2", "gulp-connect": "2.2.0", From 5ad57d160874560a61a1cf6f11d28da0e64be92a Mon Sep 17 00:00:00 2001 From: Athou Date: Tue, 2 Jun 2015 21:10:55 +0200 Subject: [PATCH 204/241] upgrade minify-css --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f8c093d0..051f2856 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "gulp": "3.8.11", "gulp-rev": "4.0.0", "gulp-rev-replace": "0.4.1", - "gulp-minify-css": "1.1.4", + "gulp-minify-css": "1.1.5", "gulp-uglify": "1.2.0", "gulp-filter": "2.0.2", "gulp-connect": "2.2.0", From 18e70a0e6b84f8fa3aeeadd8e36f234301673eb9 Mon Sep 17 00:00:00 2001 From: Athou Date: Tue, 2 Jun 2015 21:21:49 +0200 Subject: [PATCH 205/241] fix opml import without head element (fix #737) --- .../java/com/commafeed/backend/rome/OPML11Parser.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/com/commafeed/backend/rome/OPML11Parser.java b/src/main/java/com/commafeed/backend/rome/OPML11Parser.java index 472c5b4c..c947511f 100644 --- a/src/main/java/com/commafeed/backend/rome/OPML11Parser.java +++ b/src/main/java/com/commafeed/backend/rome/OPML11Parser.java @@ -1,9 +1,13 @@ package com.commafeed.backend.rome; +import java.util.Locale; + import org.jdom2.Document; import org.jdom2.Element; import com.rometools.opml.io.impl.OPML10Parser; +import com.rometools.rome.feed.WireFeed; +import com.rometools.rome.io.FeedException; /** * Support for OPML 1.1 parsing @@ -26,4 +30,10 @@ public class OPML11Parser extends OPML10Parser { return false; }; + + @Override + public WireFeed parse(Document document, boolean validate, Locale locale) throws IllegalArgumentException, FeedException { + document.getRootElement().getChildren().add(new Element("head")); + return super.parse(document, validate, locale); + } } From dcd5f3d52987704bce127057beb602bd6e3c2cf7 Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 4 Jun 2015 11:50:33 +0200 Subject: [PATCH 206/241] preserve filter when a feed is rearranged with drag&drop --- src/main/app/js/directives.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/app/js/directives.js b/src/main/app/js/directives.js index eef070be..f77e56f0 100644 --- a/src/main/app/js/directives.js +++ b/src/main/app/js/directives.js @@ -308,7 +308,8 @@ module.directive('droppable', ['CategoryService', 'FeedService', function(Catego var data = { id : source.id, - name : source.name + name : source.name, + filter : source.filter }; if (source.children) { From 9adc993472fe2ac8212efed9e31c0466b973dd25 Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 4 Jun 2015 12:28:20 +0200 Subject: [PATCH 207/241] fix youtube favicon fetching --- .openshift/config.mysql.yml | 3 + config.dev.yml | 3 + config.yml.example | 3 + pom.xml | 6 ++ .../com/commafeed/CommaFeedConfiguration.java | 2 + .../favicon/YoutubeFaviconFetcher.java | 88 +++++++++++-------- 6 files changed, 70 insertions(+), 35 deletions(-) diff --git a/.openshift/config.mysql.yml b/.openshift/config.mysql.yml index e4b9cc63..c312270d 100644 --- a/.openshift/config.mysql.yml +++ b/.openshift/config.mysql.yml @@ -13,6 +13,9 @@ app: # put your google analytics tracking code here googleAnalyticsTrackingCode: + # put your google server key (used for youtube favicon fetching) + googleAuthKey: + # number of http threads backgroundThreads: 3 diff --git a/config.dev.yml b/config.dev.yml index 5330ee20..a7771204 100644 --- a/config.dev.yml +++ b/config.dev.yml @@ -13,6 +13,9 @@ app: # put your google analytics tracking code here googleAnalyticsTrackingCode: + # put your google server key (used for youtube favicon fetching) + googleAuthKey: + # number of http threads backgroundThreads: 3 diff --git a/config.yml.example b/config.yml.example index 5f6e096d..5f6d1674 100644 --- a/config.yml.example +++ b/config.yml.example @@ -13,6 +13,9 @@ app: # put your google analytics tracking code here googleAnalyticsTrackingCode: + # put your google server key (used for youtube favicon fetching) + googleAuthKey: + # number of http threads backgroundThreads: 3 diff --git a/pom.xml b/pom.xml index a28e5ff6..6619cf8f 100644 --- a/pom.xml +++ b/pom.xml @@ -353,6 +353,12 @@ 0.9.15 + + com.google.apis + google-api-services-youtube + v3-rev138-1.20.0 + + com.h2database h2 diff --git a/src/main/java/com/commafeed/CommaFeedConfiguration.java b/src/main/java/com/commafeed/CommaFeedConfiguration.java index 7b7b619f..018b991e 100644 --- a/src/main/java/com/commafeed/CommaFeedConfiguration.java +++ b/src/main/java/com/commafeed/CommaFeedConfiguration.java @@ -77,6 +77,8 @@ public class CommaFeedConfiguration extends Configuration { private String googleAnalyticsTrackingCode; + private String googleAuthKey; + @NotNull @Min(1) @Valid diff --git a/src/main/java/com/commafeed/backend/favicon/YoutubeFaviconFetcher.java b/src/main/java/com/commafeed/backend/favicon/YoutubeFaviconFetcher.java index 81e7d3ee..2ae17c1f 100644 --- a/src/main/java/com/commafeed/backend/favicon/YoutubeFaviconFetcher.java +++ b/src/main/java/com/commafeed/backend/favicon/YoutubeFaviconFetcher.java @@ -1,18 +1,31 @@ package com.commafeed.backend.favicon; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Optional; + import javax.inject.Inject; import javax.inject.Singleton; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.select.Elements; +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URLEncodedUtils; +import com.commafeed.CommaFeedConfiguration; import com.commafeed.backend.HttpGetter; import com.commafeed.backend.HttpGetter.HttpResult; import com.commafeed.backend.model.Feed; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpRequestInitializer; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.api.services.youtube.YouTube; +import com.google.api.services.youtube.model.Channel; +import com.google.api.services.youtube.model.ChannelListResponse; +import com.google.api.services.youtube.model.Thumbnail; @Slf4j @RequiredArgsConstructor(onConstructor = @__({ @Inject })) @@ -20,40 +33,60 @@ import com.commafeed.backend.model.Feed; public class YoutubeFaviconFetcher extends AbstractFaviconFetcher { private final HttpGetter getter; + private final CommaFeedConfiguration config; @Override public byte[] fetch(Feed feed) { String url = feed.getUrl(); - if (!url.toLowerCase().contains("://gdata.youtube.com/")) { + if (!url.toLowerCase().contains("youtube.com/feeds/videos.xml")) { return null; } - String userName = extractUserName(url); - if (userName == null) { + String googleAuthKey = config.getApplicationSettings().getGoogleAuthKey(); + if (googleAuthKey == null) { + log.debug("no google auth key configured"); return null; } - String profileUrl = "https://gdata.youtube.com/feeds/users/" + userName; - byte[] bytes = null; String contentType = null; - try { - log.debug("Getting YouTube user's icon, {}", url); - - // initial get to translate username to obscure user thumbnail URL - HttpResult profileResult = getter.getBinary(profileUrl, TIMEOUT); - Document doc = Jsoup.parse(new String(profileResult.getContent()), profileUrl); - - Elements thumbnails = doc.select("media|thumbnail"); - if (thumbnails.isEmpty()) { + List params = URLEncodedUtils.parse(url.substring(url.indexOf("?") + 1), StandardCharsets.UTF_8); + Optional userId = params.stream().filter(nvp -> nvp.getName().equalsIgnoreCase("user")).findFirst(); + Optional channelId = params.stream().filter(nvp -> nvp.getName().equalsIgnoreCase("channel")).findFirst(); + System.out.println(userId.isPresent()); + if (!userId.isPresent() && !channelId.isPresent()) { return null; } - String thumbnailUrl = thumbnails.get(0).attr("abs:url"); - // final get to actually retrieve the thumbnail - HttpResult iconResult = getter.getBinary(thumbnailUrl, TIMEOUT); + YouTube youtube = new YouTube.Builder(new NetHttpTransport(), JacksonFactory.getDefaultInstance(), + new HttpRequestInitializer() { + @Override + public void initialize(HttpRequest request) throws IOException { + } + }).setApplicationName("CommaFeed").build(); + + YouTube.Channels.List list = youtube.channels().list("snippet"); + list.setKey(googleAuthKey); + if (userId.isPresent()) { + list.setForUsername(userId.get().getValue()); + } else { + list.setId(channelId.get().getValue()); + } + + log.debug("contacting youtube api"); + ChannelListResponse response = list.execute(); + if (response.getItems().isEmpty()) { + log.debug("youtube api returned no items"); + return null; + } + + Channel channel = response.getItems().get(0); + Thumbnail thumbnail = channel.getSnippet().getThumbnails().getDefault(); + + log.debug("fetching favicon"); + HttpResult iconResult = getter.getBinary(thumbnail.getUrl(), TIMEOUT); bytes = iconResult.getContent(); contentType = iconResult.getContentType(); } catch (Exception e) { @@ -65,19 +98,4 @@ public class YoutubeFaviconFetcher extends AbstractFaviconFetcher { } return bytes; } - - private String extractUserName(String url) { - int apiOrBase = url.indexOf("/users/"); - if (apiOrBase == -1) { - return null; - } - - int userEndSlash = url.indexOf('/', apiOrBase + "/users/".length()); - if (userEndSlash == -1) { - return null; - } - - return url.substring(apiOrBase + "/users/".length(), userEndSlash); - } - } From b9f2f17a24a4c165532496c50206a64c60a63fc0 Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 4 Jun 2015 14:46:59 +0200 Subject: [PATCH 208/241] angularjs 1.4.0 upgrade --- bower.json | 20 ++++++++++---------- src/main/app/js/main.js | 1 + 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/bower.json b/bower.json index 0ab5de15..9dd6b752 100644 --- a/bower.json +++ b/bower.json @@ -8,19 +8,19 @@ "lodash": "3.4.0", "bootstrap": "3.3.2", "font-awesome": "3.2.1", - "angular": "1.3.14", - "angular-resource": "1.3.14", - "angular-route": "1.3.14", - "angular-sanitize": "1.3.14", - "angular-touch": "1.3.14", - "angular-animate": "1.3.14", + "angular": "1.4.0", + "angular-resource": "1.4.0", + "angular-route": "1.4.0", + "angular-sanitize": "1.4.0", + "angular-touch": "1.4.0", + "angular-animate": "1.4.0", "angular-ui-router": "0.2.13", "angular-ui-utils": "0.1.0", "angular-ui-select2": "0.0.5", "angular-bootstrap": "0.2.0", "angular-loading-bar": "0.6.0", - "angular-translate": "2.6.1", - "angular-translate-loader-static-files": "2.6.1", + "angular-translate": "2.7.2", + "angular-translate-loader-static-files": "2.7.2", "ngInfiniteScroll": "1.0.0", "ng-grid": "2.0.6", "mousetrap": "1.4.6", @@ -31,7 +31,7 @@ "swagger-ui": "2.1.8-M1" }, "resolutions": { - "angular": "1.3.14", - "angular-translate": "2.6.1" + "angular": "1.4.0", + "angular-translate": "2.7.2" } } diff --git a/src/main/app/js/main.js b/src/main/app/js/main.js index 8a51e0d0..d599ed93 100644 --- a/src/main/app/js/main.js +++ b/src/main/app/js/main.js @@ -13,6 +13,7 @@ app.config([ function($routeProvider, $stateProvider, $urlRouterProvider, $httpProvider, $compileProvider, cfpLoadingBarProvider, $translateProvider) { + $translateProvider.useSanitizeValueStrategy('sanitize'); $translateProvider.useStaticFilesLoader({ prefix : 'i18n/', suffix : '.js' From 5713a78f2e62489fb6c9ff166b4b85ed40ba5f44 Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 4 Jun 2015 14:52:15 +0200 Subject: [PATCH 209/241] exclude guava-jdk5 as guava is already included --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index 6619cf8f..f0a3f7fc 100644 --- a/pom.xml +++ b/pom.xml @@ -357,6 +357,12 @@ com.google.apis google-api-services-youtube v3-rev138-1.20.0 + + + com.google.guava + guava-jdk5 + + From 06373480ae3de6d58cc3d061ba02a9292f5a5323 Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 4 Jun 2015 14:55:36 +0200 Subject: [PATCH 210/241] various upgrades --- pom.xml | 10 +++++----- src/main/java/com/commafeed/backend/HttpGetter.java | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index f0a3f7fc..ecff5f05 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ 1.8 0.8.1 4.0 - 3.6.3 + 3.6.4 1.5.0 @@ -238,7 +238,7 @@ org.apache.httpcomponents httpclient - 4.4.1 + 4.5 @@ -350,13 +350,13 @@ net.sourceforge.cssparser cssparser - 0.9.15 + 0.9.16 com.google.apis google-api-services-youtube - v3-rev138-1.20.0 + v3-rev139-1.20.0 com.google.guava @@ -395,7 +395,7 @@ org.mockito mockito-core - 2.0.7-beta + 2.0.11-beta test diff --git a/src/main/java/com/commafeed/backend/HttpGetter.java b/src/main/java/com/commafeed/backend/HttpGetter.java index d26aec84..a2ac67da 100644 --- a/src/main/java/com/commafeed/backend/HttpGetter.java +++ b/src/main/java/com/commafeed/backend/HttpGetter.java @@ -181,7 +181,7 @@ public class HttpGetter { builder.addInterceptorFirst(REMOVE_INCORRECT_CONTENT_ENCODING); builder.disableAutomaticRetries(); - builder.setSslcontext(SSL_CONTEXT); + builder.setSSLContext(SSL_CONTEXT); builder.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE); RequestConfig.Builder configBuilder = RequestConfig.custom(); From 040cdde8baf1a5facd25ae1ab26464e4be940b7e Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 4 Jun 2015 15:05:23 +0200 Subject: [PATCH 211/241] jcl-over-slf4j is already included --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index ecff5f05..ebac3014 100644 --- a/pom.xml +++ b/pom.xml @@ -239,6 +239,12 @@ org.apache.httpcomponents httpclient 4.5 + + + commons-logging + commons-logging + + From dfbd556bb809d9af61abd577628d0fb12e10035c Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 8 Jun 2015 06:59:05 +0200 Subject: [PATCH 212/241] Revert "angularjs 1.4.0 upgrade", fixes android navigation (fix #739) --- bower.json | 20 ++++++++++---------- src/main/app/js/main.js | 1 - 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/bower.json b/bower.json index 9dd6b752..0ab5de15 100644 --- a/bower.json +++ b/bower.json @@ -8,19 +8,19 @@ "lodash": "3.4.0", "bootstrap": "3.3.2", "font-awesome": "3.2.1", - "angular": "1.4.0", - "angular-resource": "1.4.0", - "angular-route": "1.4.0", - "angular-sanitize": "1.4.0", - "angular-touch": "1.4.0", - "angular-animate": "1.4.0", + "angular": "1.3.14", + "angular-resource": "1.3.14", + "angular-route": "1.3.14", + "angular-sanitize": "1.3.14", + "angular-touch": "1.3.14", + "angular-animate": "1.3.14", "angular-ui-router": "0.2.13", "angular-ui-utils": "0.1.0", "angular-ui-select2": "0.0.5", "angular-bootstrap": "0.2.0", "angular-loading-bar": "0.6.0", - "angular-translate": "2.7.2", - "angular-translate-loader-static-files": "2.7.2", + "angular-translate": "2.6.1", + "angular-translate-loader-static-files": "2.6.1", "ngInfiniteScroll": "1.0.0", "ng-grid": "2.0.6", "mousetrap": "1.4.6", @@ -31,7 +31,7 @@ "swagger-ui": "2.1.8-M1" }, "resolutions": { - "angular": "1.4.0", - "angular-translate": "2.7.2" + "angular": "1.3.14", + "angular-translate": "2.6.1" } } diff --git a/src/main/app/js/main.js b/src/main/app/js/main.js index d599ed93..8a51e0d0 100644 --- a/src/main/app/js/main.js +++ b/src/main/app/js/main.js @@ -13,7 +13,6 @@ app.config([ function($routeProvider, $stateProvider, $urlRouterProvider, $httpProvider, $compileProvider, cfpLoadingBarProvider, $translateProvider) { - $translateProvider.useSanitizeValueStrategy('sanitize'); $translateProvider.useStaticFilesLoader({ prefix : 'i18n/', suffix : '.js' From 18a7bd1fd1a83b3b8d1b245e32f78c0b4443b7a7 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 8 Jun 2015 15:37:58 +0200 Subject: [PATCH 213/241] check both urls for favicon --- .../commafeed/backend/favicon/DefaultFaviconFetcher.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/commafeed/backend/favicon/DefaultFaviconFetcher.java b/src/main/java/com/commafeed/backend/favicon/DefaultFaviconFetcher.java index 9645f793..0dd242d7 100644 --- a/src/main/java/com/commafeed/backend/favicon/DefaultFaviconFetcher.java +++ b/src/main/java/com/commafeed/backend/favicon/DefaultFaviconFetcher.java @@ -29,8 +29,14 @@ public class DefaultFaviconFetcher extends AbstractFaviconFetcher { @Override public byte[] fetch(Feed feed) { - String url = feed.getLink() != null ? feed.getLink() : feed.getUrl(); + byte[] icon = fetch(feed.getLink()); + if (icon == null) { + icon = fetch(feed.getUrl()); + } + return icon; + } + private byte[] fetch(String url) { if (url == null) { log.debug("url is null"); return null; From 101602c6f676f9239ed3340825c892018e46aa36 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 8 Jun 2015 15:53:09 +0200 Subject: [PATCH 214/241] return the correct media type for favicons (fix #736) --- .../favicon/AbstractFaviconFetcher.java | 11 ++++++++++- .../backend/favicon/DefaultFaviconFetcher.java | 18 +++++++++--------- .../favicon/FacebookFaviconFetcher.java | 6 +++--- .../backend/favicon/YoutubeFaviconFetcher.java | 6 +++--- .../commafeed/backend/service/FeedService.java | 9 +++++---- .../commafeed/frontend/resource/FeedREST.java | 6 ++++-- 6 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/commafeed/backend/favicon/AbstractFaviconFetcher.java b/src/main/java/com/commafeed/backend/favicon/AbstractFaviconFetcher.java index ff71535c..5d6b0dc0 100644 --- a/src/main/java/com/commafeed/backend/favicon/AbstractFaviconFetcher.java +++ b/src/main/java/com/commafeed/backend/favicon/AbstractFaviconFetcher.java @@ -3,6 +3,8 @@ package com.commafeed.backend.favicon; import java.util.Arrays; import java.util.List; +import lombok.Getter; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -18,7 +20,7 @@ public abstract class AbstractFaviconFetcher { protected static int TIMEOUT = 4000; - public abstract byte[] fetch(Feed feed); + public abstract Favicon fetch(Feed feed); protected boolean isValidIconResponse(byte[] content, String contentType) { if (content == null) { @@ -48,4 +50,11 @@ public abstract class AbstractFaviconFetcher { return true; } + + @RequiredArgsConstructor + @Getter + public static class Favicon { + private final byte[] icon; + private final String mediaType; + } } diff --git a/src/main/java/com/commafeed/backend/favicon/DefaultFaviconFetcher.java b/src/main/java/com/commafeed/backend/favicon/DefaultFaviconFetcher.java index 0dd242d7..65d68364 100644 --- a/src/main/java/com/commafeed/backend/favicon/DefaultFaviconFetcher.java +++ b/src/main/java/com/commafeed/backend/favicon/DefaultFaviconFetcher.java @@ -28,15 +28,15 @@ public class DefaultFaviconFetcher extends AbstractFaviconFetcher { private final HttpGetter getter; @Override - public byte[] fetch(Feed feed) { - byte[] icon = fetch(feed.getLink()); + public Favicon fetch(Feed feed) { + Favicon icon = fetch(feed.getLink()); if (icon == null) { icon = fetch(feed.getUrl()); } return icon; } - private byte[] fetch(String url) { + private Favicon fetch(String url) { if (url == null) { log.debug("url is null"); return null; @@ -53,7 +53,7 @@ public class DefaultFaviconFetcher extends AbstractFaviconFetcher { url = url.substring(0, firstSlash); } - byte[] icon = getIconAtRoot(url); + Favicon icon = getIconAtRoot(url); if (icon == null) { icon = getIconInPage(url); @@ -62,7 +62,7 @@ public class DefaultFaviconFetcher extends AbstractFaviconFetcher { return icon; } - private byte[] getIconAtRoot(String url) { + private Favicon getIconAtRoot(String url) { byte[] bytes = null; String contentType = null; @@ -78,12 +78,12 @@ public class DefaultFaviconFetcher extends AbstractFaviconFetcher { } if (!isValidIconResponse(bytes, contentType)) { - bytes = null; + return null; } - return bytes; + return new Favicon(bytes, contentType); } - private byte[] getIconInPage(String url) { + private Favicon getIconInPage(String url) { Document doc = null; try { @@ -127,6 +127,6 @@ public class DefaultFaviconFetcher extends AbstractFaviconFetcher { return null; } - return bytes; + return new Favicon(bytes, contentType); } } diff --git a/src/main/java/com/commafeed/backend/favicon/FacebookFaviconFetcher.java b/src/main/java/com/commafeed/backend/favicon/FacebookFaviconFetcher.java index 092fe0cb..6b1fe956 100644 --- a/src/main/java/com/commafeed/backend/favicon/FacebookFaviconFetcher.java +++ b/src/main/java/com/commafeed/backend/favicon/FacebookFaviconFetcher.java @@ -26,7 +26,7 @@ public class FacebookFaviconFetcher extends AbstractFaviconFetcher { private final HttpGetter getter; @Override - public byte[] fetch(Feed feed) { + public Favicon fetch(Feed feed) { String url = feed.getUrl(); if (!url.toLowerCase().contains("www.facebook.com")) { @@ -54,9 +54,9 @@ public class FacebookFaviconFetcher extends AbstractFaviconFetcher { } if (!isValidIconResponse(bytes, contentType)) { - bytes = null; + return null; } - return bytes; + return new Favicon(bytes, contentType); } private String extractUserName(String url) { diff --git a/src/main/java/com/commafeed/backend/favicon/YoutubeFaviconFetcher.java b/src/main/java/com/commafeed/backend/favicon/YoutubeFaviconFetcher.java index 2ae17c1f..dfaca7ee 100644 --- a/src/main/java/com/commafeed/backend/favicon/YoutubeFaviconFetcher.java +++ b/src/main/java/com/commafeed/backend/favicon/YoutubeFaviconFetcher.java @@ -36,7 +36,7 @@ public class YoutubeFaviconFetcher extends AbstractFaviconFetcher { private final CommaFeedConfiguration config; @Override - public byte[] fetch(Feed feed) { + public Favicon fetch(Feed feed) { String url = feed.getUrl(); if (!url.toLowerCase().contains("youtube.com/feeds/videos.xml")) { @@ -94,8 +94,8 @@ public class YoutubeFaviconFetcher extends AbstractFaviconFetcher { } if (!isValidIconResponse(bytes, contentType)) { - bytes = null; + return null; } - return bytes; + return new Favicon(bytes, contentType); } } diff --git a/src/main/java/com/commafeed/backend/service/FeedService.java b/src/main/java/com/commafeed/backend/service/FeedService.java index 59aaba30..2ee312af 100644 --- a/src/main/java/com/commafeed/backend/service/FeedService.java +++ b/src/main/java/com/commafeed/backend/service/FeedService.java @@ -12,6 +12,7 @@ import org.apache.commons.io.IOUtils; import com.commafeed.backend.dao.FeedDAO; import com.commafeed.backend.favicon.AbstractFaviconFetcher; +import com.commafeed.backend.favicon.AbstractFaviconFetcher.Favicon; import com.commafeed.backend.feed.FeedUtils; import com.commafeed.backend.model.Feed; @@ -21,7 +22,7 @@ public class FeedService { private final FeedDAO feedDAO; private final Set faviconFetchers; - private byte[] defaultFavicon; + private Favicon defaultFavicon; @Inject public FeedService(FeedDAO feedDAO, Set faviconFetchers) { @@ -29,7 +30,7 @@ public class FeedService { this.faviconFetchers = faviconFetchers; try { - defaultFavicon = IOUtils.toByteArray(getClass().getResource("/images/default_favicon.gif")); + defaultFavicon = new Favicon(IOUtils.toByteArray(getClass().getResource("/images/default_favicon.gif")), "image/gif"); } catch (IOException e) { throw new RuntimeException("could not load default favicon", e); } @@ -49,9 +50,9 @@ public class FeedService { return feed; } - public byte[] fetchFavicon(Feed feed) { + public Favicon fetchFavicon(Feed feed) { - byte[] icon = null; + Favicon icon = null; for (AbstractFaviconFetcher faviconFetcher : faviconFetchers) { icon = faviconFetcher.fetch(feed); if (icon != null) { diff --git a/src/main/java/com/commafeed/frontend/resource/FeedREST.java b/src/main/java/com/commafeed/frontend/resource/FeedREST.java index aa2fc04a..4029bc78 100644 --- a/src/main/java/com/commafeed/frontend/resource/FeedREST.java +++ b/src/main/java/com/commafeed/frontend/resource/FeedREST.java @@ -12,6 +12,7 @@ import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.stream.Collectors; import javax.inject.Inject; @@ -45,6 +46,7 @@ import com.commafeed.backend.cache.CacheService; import com.commafeed.backend.dao.FeedCategoryDAO; import com.commafeed.backend.dao.FeedEntryStatusDAO; import com.commafeed.backend.dao.FeedSubscriptionDAO; +import com.commafeed.backend.favicon.AbstractFaviconFetcher.Favicon; import com.commafeed.backend.feed.FeedEntryKeyword; import com.commafeed.backend.feed.FeedFetcher; import com.commafeed.backend.feed.FeedQueues; @@ -339,9 +341,9 @@ public class FeedREST { return Response.status(Status.NOT_FOUND).build(); } Feed feed = subscription.getFeed(); - byte[] icon = feedService.fetchFavicon(feed); + Favicon icon = feedService.fetchFavicon(feed); - ResponseBuilder builder = Response.ok(icon, "image/x-icon"); + ResponseBuilder builder = Response.ok(icon.getIcon(), Optional.ofNullable(icon.getMediaType()).orElse("image/x-icon")); CacheControl cacheControl = new CacheControl(); cacheControl.setMaxAge(2592000); From c24e9e083ccb782e4f160f2e0d2ee2428cf11430 Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 19 Jun 2015 08:25:12 +0200 Subject: [PATCH 215/241] changelog update --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 648b2c73..25deec3c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,7 @@ v 2.2.0 + - fix youtube and instagram favicon fetching + - mark as read filter was lost when a feed was rearranged with drag&drop + - feed entry categories are now displayed if available - various performance and dependencies upgrades - java8 is now required v 2.1.0 From a16d9877cc932219f3a298ea86aa3d61e79f4fa4 Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 19 Jun 2015 08:26:08 +0200 Subject: [PATCH 216/241] 2.2.0 release --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ebac3014..34e5ec5d 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.commafeed commafeed - 2.2.0-SNAPSHOT + 2.2.0 jar CommaFeed From f73ddc03e90c8b70f637485280df215a2dfcd7a7 Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 19 Jun 2015 08:32:25 +0200 Subject: [PATCH 217/241] readme update --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c7d95b59..b5722711 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,14 @@ Browser extensions: [Chrome](https://github.com/Athou/commafeed-chrome) - [Firef ## Deployment on your own server +### The very short version (download precompiled package) + + mkdir commafeed && cd commafeed + wget https://github.com/Athou/commafeed/releases/download/2.2.0/commafeed.jar + wget https://raw.githubusercontent.com/Athou/commafeed/2.2.0/config.yml.example -O config.yml + vi config.yml + java -Djava.net.preferIPv4Stack=true -jar commafeed.jar server config.yml + ### The short version git clone https://github.com/Athou/commafeed.git @@ -112,7 +120,7 @@ Steps to configuring a development environment for CommaFeed may include, but ma ## Copyright and license -Copyright 2013-2014 CommaFeed. +Copyright 2013-2015 CommaFeed. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with the License. From 84f51603fb575e1f07a6d88026c84da612a34470 Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 19 Jun 2015 08:33:43 +0200 Subject: [PATCH 218/241] bump version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 34e5ec5d..70462572 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.commafeed commafeed - 2.2.0 + 2.3.0-SNAPSHOT jar CommaFeed From bae5c67dfa97cdc73cd29182a02724ca6113e90e Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 19 Jun 2015 08:41:23 +0200 Subject: [PATCH 219/241] dropwizard upgrade --- pom.xml | 2 +- .../java/com/commafeed/frontend/resource/UserREST.java | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 70462572..486f3cb6 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ UTF-8 1.8 - 0.8.1 + 0.9.0-rc1 4.0 3.6.4 1.5.0 diff --git a/src/main/java/com/commafeed/frontend/resource/UserREST.java b/src/main/java/com/commafeed/frontend/resource/UserREST.java index 58c85e52..de269c67 100644 --- a/src/main/java/com/commafeed/frontend/resource/UserREST.java +++ b/src/main/java/com/commafeed/frontend/resource/UserREST.java @@ -4,14 +4,12 @@ import io.dropwizard.hibernate.UnitOfWork; import io.dropwizard.jersey.validation.ValidationErrorMessage; import java.util.Arrays; -import java.util.Collections; import java.util.Date; import java.util.Optional; import java.util.UUID; import javax.inject.Inject; import javax.inject.Singleton; -import javax.validation.ConstraintViolation; import javax.validation.Valid; import javax.ws.rs.Consumes; import javax.ws.rs.GET; @@ -230,12 +228,8 @@ public class UserREST { sessionHelper.setLoggedInUser(registeredUser); return Response.ok().build(); } catch (final IllegalArgumentException e) { - return Response.status(422).entity(new ValidationErrorMessage(Collections.> emptySet()) { - @Override - public ImmutableList getErrors() { - return ImmutableList.of(e.getMessage()); - } - }).type(MediaType.TEXT_PLAIN).build(); + return Response.status(422).entity(new ValidationErrorMessage(ImmutableList.of(e.getMessage()))).type(MediaType.TEXT_PLAIN) + .build(); } } From ddfd170ea89e25014a82c8e081897b7a68a1aa46 Mon Sep 17 00:00:00 2001 From: Athou Date: Wed, 24 Jun 2015 11:42:01 +0200 Subject: [PATCH 220/241] make eclipse mars happy --- .../com/commafeed/backend/dao/UnitOfWork.java | 4 +- .../commafeed/backend/feed/FeedQueues.java | 2 +- .../backend/feed/FeedRefreshUpdater.java | 11 ++-- .../service/DatabaseCleaningService.java | 22 ++++---- .../backend/service/StartupService.java | 29 +++++----- .../frontend/servlet/CustomCssServlet.java | 8 +-- .../frontend/servlet/NextUnreadServlet.java | 54 +++++++++---------- 7 files changed, 63 insertions(+), 67 deletions(-) diff --git a/src/main/java/com/commafeed/backend/dao/UnitOfWork.java b/src/main/java/com/commafeed/backend/dao/UnitOfWork.java index d4f2ac41..ae21c4a8 100644 --- a/src/main/java/com/commafeed/backend/dao/UnitOfWork.java +++ b/src/main/java/com/commafeed/backend/dao/UnitOfWork.java @@ -18,13 +18,13 @@ public class UnitOfWork { } public static void run(SessionFactory sessionFactory, SessionRunner sessionRunner) { - run(sessionFactory, () -> { + call(sessionFactory, () -> { sessionRunner.runInSession(); return null; }); } - public static T run(SessionFactory sessionFactory, SessionRunnerReturningValue sessionRunner) { + public static T call(SessionFactory sessionFactory, SessionRunnerReturningValue sessionRunner) { final Session session = sessionFactory.openSession(); if (ManagedSessionContext.hasBind(sessionFactory)) { throw new IllegalStateException("Already in a unit of work!"); diff --git a/src/main/java/com/commafeed/backend/feed/FeedQueues.java b/src/main/java/com/commafeed/backend/feed/FeedQueues.java index 15d9bf7d..f7b3a471 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedQueues.java +++ b/src/main/java/com/commafeed/backend/feed/FeedQueues.java @@ -108,7 +108,7 @@ public class FeedQueues { // add feeds that are up to refresh from the database int count = batchSize - contexts.size(); if (count > 0) { - List feeds = UnitOfWork.run(sessionFactory, () -> feedDAO.findNextUpdatable(count, getLastLoginThreshold())); + List feeds = UnitOfWork.call(sessionFactory, () -> feedDAO.findNextUpdatable(count, getLastLoginThreshold())); for (Feed feed : feeds) { contexts.add(new FeedRefreshContext(feed, false)); } diff --git a/src/main/java/com/commafeed/backend/feed/FeedRefreshUpdater.java b/src/main/java/com/commafeed/backend/feed/FeedRefreshUpdater.java index 14317cf3..08765228 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedRefreshUpdater.java +++ b/src/main/java/com/commafeed/backend/feed/FeedRefreshUpdater.java @@ -1,7 +1,5 @@ package com.commafeed.backend.feed; -import io.dropwizard.lifecycle.Managed; - import java.util.ArrayList; import java.util.Arrays; import java.util.Date; @@ -14,8 +12,6 @@ import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Singleton; -import lombok.extern.slf4j.Slf4j; - import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -39,6 +35,9 @@ import com.commafeed.backend.service.FeedUpdateService; import com.commafeed.backend.service.PubSubService; import com.google.common.util.concurrent.Striped; +import io.dropwizard.lifecycle.Managed; +import lombok.extern.slf4j.Slf4j; + @Slf4j @Singleton public class FeedRefreshUpdater implements Managed { @@ -121,7 +120,7 @@ public class FeedRefreshUpdater implements Managed { if (!lastEntries.contains(cacheKey)) { log.debug("cache miss for {}", entry.getUrl()); if (subscriptions == null) { - subscriptions = UnitOfWork.run(sessionFactory, () -> feedSubscriptionDAO.findByFeed(feed)); + subscriptions = UnitOfWork.call(sessionFactory, () -> feedSubscriptionDAO.findByFeed(feed)); } ok &= addEntry(feed, entry, subscriptions); entryCacheMiss.mark(); @@ -183,7 +182,7 @@ public class FeedRefreshUpdater implements Managed { locked1 = lock1.tryLock(1, TimeUnit.MINUTES); locked2 = lock2.tryLock(1, TimeUnit.MINUTES); if (locked1 && locked2) { - boolean inserted = UnitOfWork.run(sessionFactory, () -> feedUpdateService.addEntry(feed, entry, subscriptions)); + boolean inserted = UnitOfWork.call(sessionFactory, () -> feedUpdateService.addEntry(feed, entry, subscriptions)); if (inserted) { entryInserted.mark(); } diff --git a/src/main/java/com/commafeed/backend/service/DatabaseCleaningService.java b/src/main/java/com/commafeed/backend/service/DatabaseCleaningService.java index d13ec2b6..c79fbef0 100644 --- a/src/main/java/com/commafeed/backend/service/DatabaseCleaningService.java +++ b/src/main/java/com/commafeed/backend/service/DatabaseCleaningService.java @@ -6,9 +6,6 @@ import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - import org.hibernate.SessionFactory; import com.commafeed.backend.dao.FeedDAO; @@ -19,12 +16,15 @@ import com.commafeed.backend.dao.FeedEntryStatusDAO; import com.commafeed.backend.dao.UnitOfWork; import com.commafeed.backend.model.Feed; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + /** * Contains utility methods for cleaning the database * */ @Slf4j -@RequiredArgsConstructor(onConstructor = @__({ @Inject })) +@RequiredArgsConstructor(onConstructor = @__({ @Inject }) ) @Singleton public class DatabaseCleaningService { @@ -42,16 +42,16 @@ public class DatabaseCleaningService { int deleted = 0; long entriesTotal = 0; do { - List feeds = UnitOfWork.run(sessionFactory, () -> feedDAO.findWithoutSubscriptions(1)); + List feeds = UnitOfWork.call(sessionFactory, () -> feedDAO.findWithoutSubscriptions(1)); for (Feed feed : feeds) { int entriesDeleted = 0; do { - entriesDeleted = UnitOfWork.run(sessionFactory, () -> feedEntryDAO.delete(feed.getId(), BATCH_SIZE)); + entriesDeleted = UnitOfWork.call(sessionFactory, () -> feedEntryDAO.delete(feed.getId(), BATCH_SIZE)); entriesTotal += entriesDeleted; log.info("removed {} entries for feeds without subscriptions", entriesTotal); } while (entriesDeleted > 0); } - deleted = UnitOfWork.run(sessionFactory, () -> feedDAO.delete(feeds)); + deleted = UnitOfWork.call(sessionFactory, () -> feedDAO.delete(feeds)); total += deleted; log.info("removed {} feeds without subscriptions", total); } while (deleted != 0); @@ -64,7 +64,7 @@ public class DatabaseCleaningService { long total = 0; int deleted = 0; do { - deleted = UnitOfWork.run(sessionFactory, () -> feedEntryContentDAO.deleteWithoutEntries(BATCH_SIZE)); + deleted = UnitOfWork.call(sessionFactory, () -> feedEntryContentDAO.deleteWithoutEntries(BATCH_SIZE)); total += deleted; log.info("removed {} contents without entries", total); } while (deleted != 0); @@ -75,7 +75,7 @@ public class DatabaseCleaningService { public long cleanEntriesForFeedsExceedingCapacity(final int maxFeedCapacity) { long total = 0; while (true) { - List feeds = UnitOfWork.run(sessionFactory, + List feeds = UnitOfWork.call(sessionFactory, () -> feedEntryDAO.findFeedsExceedingCapacity(maxFeedCapacity, BATCH_SIZE)); if (feeds.isEmpty()) { break; @@ -85,7 +85,7 @@ public class DatabaseCleaningService { long remaining = feed.getCapacity() - maxFeedCapacity; do { final long rem = remaining; - int deleted = UnitOfWork.run(sessionFactory, + int deleted = UnitOfWork.call(sessionFactory, () -> feedEntryDAO.deleteOldEntries(feed.getId(), Math.min(BATCH_SIZE, rem))); total += deleted; remaining -= deleted; @@ -102,7 +102,7 @@ public class DatabaseCleaningService { long total = 0; int deleted = 0; do { - deleted = UnitOfWork.run(sessionFactory, + deleted = UnitOfWork.call(sessionFactory, () -> feedEntryStatusDAO.delete(feedEntryStatusDAO.getOldStatuses(olderThan, BATCH_SIZE))); total += deleted; log.info("removed {} old read statuses", total); diff --git a/src/main/java/com/commafeed/backend/service/StartupService.java b/src/main/java/com/commafeed/backend/service/StartupService.java index 4c914818..426d37d5 100644 --- a/src/main/java/com/commafeed/backend/service/StartupService.java +++ b/src/main/java/com/commafeed/backend/service/StartupService.java @@ -1,7 +1,5 @@ package com.commafeed.backend.service; -import io.dropwizard.lifecycle.Managed; - import java.sql.Connection; import java.util.Arrays; @@ -9,17 +7,6 @@ import javax.inject.Inject; import javax.inject.Singleton; import javax.sql.DataSource; -import liquibase.Liquibase; -import liquibase.database.Database; -import liquibase.database.DatabaseFactory; -import liquibase.database.core.PostgresDatabase; -import liquibase.database.jvm.JdbcConnection; -import liquibase.resource.ClassLoaderResourceAccessor; -import liquibase.resource.ResourceAccessor; -import liquibase.structure.DatabaseObject; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - import org.hibernate.SessionFactory; import org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; @@ -31,8 +18,20 @@ import com.commafeed.backend.dao.UnitOfWork; import com.commafeed.backend.dao.UserDAO; import com.commafeed.backend.model.UserRole.Role; +import io.dropwizard.lifecycle.Managed; +import liquibase.Liquibase; +import liquibase.database.Database; +import liquibase.database.DatabaseFactory; +import liquibase.database.core.PostgresDatabase; +import liquibase.database.jvm.JdbcConnection; +import liquibase.resource.ClassLoaderResourceAccessor; +import liquibase.resource.ResourceAccessor; +import liquibase.structure.DatabaseObject; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + @Slf4j -@RequiredArgsConstructor(onConstructor = @__({ @Inject })) +@RequiredArgsConstructor(onConstructor = @__({ @Inject }) ) @Singleton public class StartupService implements Managed { @@ -44,7 +43,7 @@ public class StartupService implements Managed { @Override public void start() throws Exception { updateSchema(); - long count = UnitOfWork.run(sessionFactory, () -> userDAO.count()); + long count = UnitOfWork.call(sessionFactory, () -> userDAO.count()); if (count == 0) { UnitOfWork.run(sessionFactory, () -> initialData()); } diff --git a/src/main/java/com/commafeed/frontend/servlet/CustomCssServlet.java b/src/main/java/com/commafeed/frontend/servlet/CustomCssServlet.java index 22f51db9..d3218653 100644 --- a/src/main/java/com/commafeed/frontend/servlet/CustomCssServlet.java +++ b/src/main/java/com/commafeed/frontend/servlet/CustomCssServlet.java @@ -10,8 +10,6 @@ import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; - import org.hibernate.SessionFactory; import com.commafeed.backend.dao.UnitOfWork; @@ -20,8 +18,10 @@ import com.commafeed.backend.model.User; import com.commafeed.backend.model.UserSettings; import com.commafeed.frontend.session.SessionHelper; +import lombok.RequiredArgsConstructor; + @SuppressWarnings("serial") -@RequiredArgsConstructor(onConstructor = @__({ @Inject })) +@RequiredArgsConstructor(onConstructor = @__({ @Inject }) ) @Singleton public class CustomCssServlet extends HttpServlet { @@ -37,7 +37,7 @@ public class CustomCssServlet extends HttpServlet { return; } - UserSettings settings = UnitOfWork.run(sessionFactory, () -> userSettingsDAO.findByUser(user.get())); + UserSettings settings = UnitOfWork.call(sessionFactory, () -> userSettingsDAO.findByUser(user.get())); if (settings == null || settings.getCustomCss() == null) { return; } diff --git a/src/main/java/com/commafeed/frontend/servlet/NextUnreadServlet.java b/src/main/java/com/commafeed/frontend/servlet/NextUnreadServlet.java index 990b30e1..4ea60674 100644 --- a/src/main/java/com/commafeed/frontend/servlet/NextUnreadServlet.java +++ b/src/main/java/com/commafeed/frontend/servlet/NextUnreadServlet.java @@ -11,8 +11,6 @@ import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; - import org.apache.commons.lang3.StringUtils; import org.hibernate.SessionFactory; @@ -31,8 +29,10 @@ import com.commafeed.frontend.resource.CategoryREST; import com.commafeed.frontend.session.SessionHelper; import com.google.common.collect.Iterables; +import lombok.RequiredArgsConstructor; + @SuppressWarnings("serial") -@RequiredArgsConstructor(onConstructor = @__({ @Inject })) +@RequiredArgsConstructor(onConstructor = @__({ @Inject }) ) @Singleton public class NextUnreadServlet extends HttpServlet { @@ -63,31 +63,29 @@ public class NextUnreadServlet extends HttpServlet { final ReadingOrder order = StringUtils.equals(orderParam, "asc") ? ReadingOrder.asc : ReadingOrder.desc; - FeedEntryStatus status = UnitOfWork.run( - sessionFactory, - () -> { - FeedEntryStatus s = null; - if (StringUtils.isBlank(categoryId) || CategoryREST.ALL.equals(categoryId)) { - List subs = feedSubscriptionDAO.findAll(user.get()); - List statuses = feedEntryStatusDAO.findBySubscriptions(user.get(), subs, true, null, null, 0, 1, - order, true, false, null); - s = Iterables.getFirst(statuses, null); - } else { - FeedCategory category = feedCategoryDAO.findById(user.get(), Long.valueOf(categoryId)); - if (category != null) { - List children = feedCategoryDAO.findAllChildrenCategories(user.get(), category); - List subscriptions = feedSubscriptionDAO.findByCategories(user.get(), children); - List statuses = feedEntryStatusDAO.findBySubscriptions(user.get(), subscriptions, true, null, - null, 0, 1, order, true, false, null); - s = Iterables.getFirst(statuses, null); - } - } - if (s != null) { - s.setRead(true); - feedEntryStatusDAO.saveOrUpdate(s); - } - return s; - }); + FeedEntryStatus status = UnitOfWork.call(sessionFactory, () -> { + FeedEntryStatus s = null; + if (StringUtils.isBlank(categoryId) || CategoryREST.ALL.equals(categoryId)) { + List subs = feedSubscriptionDAO.findAll(user.get()); + List statuses = feedEntryStatusDAO.findBySubscriptions(user.get(), subs, true, null, null, 0, 1, order, + true, false, null); + s = Iterables.getFirst(statuses, null); + } else { + FeedCategory category = feedCategoryDAO.findById(user.get(), Long.valueOf(categoryId)); + if (category != null) { + List children = feedCategoryDAO.findAllChildrenCategories(user.get(), category); + List subscriptions = feedSubscriptionDAO.findByCategories(user.get(), children); + List statuses = feedEntryStatusDAO.findBySubscriptions(user.get(), subscriptions, true, null, null, 0, + 1, order, true, false, null); + s = Iterables.getFirst(statuses, null); + } + } + if (s != null) { + s.setRead(true); + feedEntryStatusDAO.saveOrUpdate(s); + } + return s; + }); if (status == null) { resp.sendRedirect(resp.encodeRedirectURL(config.getApplicationSettings().getPublicUrl())); From fed7a1ac849a29ab18670b5691ee9fa2bbf2e1c8 Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 25 Jun 2015 11:20:50 +0200 Subject: [PATCH 221/241] rewrite query using subqueries --- .../java/com/commafeed/backend/dao/FeedDAO.java | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/commafeed/backend/dao/FeedDAO.java b/src/main/java/com/commafeed/backend/dao/FeedDAO.java index 9a4e2795..e522437c 100644 --- a/src/main/java/com/commafeed/backend/dao/FeedDAO.java +++ b/src/main/java/com/commafeed/backend/dao/FeedDAO.java @@ -15,7 +15,6 @@ import com.commafeed.backend.model.QFeed; import com.commafeed.backend.model.QFeedSubscription; import com.commafeed.backend.model.QUser; import com.google.common.collect.Iterables; -import com.mysema.query.BooleanBuilder; import com.mysema.query.jpa.hibernate.HibernateQuery; import com.mysema.query.jpa.hibernate.HibernateSubQuery; @@ -30,19 +29,16 @@ public class FeedDAO extends GenericDAO { } public List findNextUpdatable(int count, Date lastLoginThreshold) { - BooleanBuilder disabledDatePredicate = new BooleanBuilder(); - disabledDatePredicate.or(feed.disabledUntil.isNull()); - disabledDatePredicate.or(feed.disabledUntil.lt(new Date())); + HibernateQuery query = newQuery().from(feed); + query.where(feed.disabledUntil.isNull().or(feed.disabledUntil.lt(new Date()))); - HibernateQuery query = null; if (lastLoginThreshold != null) { QFeedSubscription subs = QFeedSubscription.feedSubscription; QUser user = QUser.user; - query = newQuery().from(subs); - query.join(subs.feed, feed).join(subs.user, user).where(disabledDatePredicate, user.lastLogin.gt(lastLoginThreshold)); - } else { - query = newQuery().from(feed); - query.where(disabledDatePredicate); + + HibernateSubQuery subQuery = new HibernateSubQuery().from(subs); + subQuery.join(subs.user, user).where(user.lastLogin.gt(lastLoginThreshold)); + query.where(subQuery.exists()); } return query.orderBy(feed.disabledUntil.asc()).limit(count).distinct().list(feed); From 532d671feb705b6d2913acc0bbf204e03cc0d7c6 Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 26 Jun 2015 14:01:42 +0200 Subject: [PATCH 222/241] upgrade frontend-maven-plugin (fix #743) --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 486f3cb6..11c039f5 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ CommaFeed - 3.0.5 + 3.1.0 @@ -132,7 +132,7 @@ com.github.eirslett frontend-maven-plugin - 0.0.22 + 0.0.24 install node and npm From 4007f37492000330f9e25562171bbf70740d3383 Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 26 Jun 2015 14:13:14 +0200 Subject: [PATCH 223/241] maven 3.3 setup instructions --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b5722711..fff390ae 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,11 @@ Browser extensions: [Chrome](https://github.com/Athou/commafeed-chrome) - [Firef ### The short version git clone https://github.com/Athou/commafeed.git - cd commafeed + cd commafeed mvn clean package - cp config.yml.example config.yml - vi config.yml - java -Djava.net.preferIPv4Stack=true -jar target/commafeed.jar server config.yml + cp config.yml.example config.yml + vi config.yml + java -Djava.net.preferIPv4Stack=true -jar target/commafeed.jar server config.yml ### The long version @@ -39,13 +39,15 @@ You also need Maven 3.x (and a Java 1.8+ JDK) installed in order to build the ap To install maven and openjdk on Ubuntu, issue the following commands - sudo apt-get install g++ build-essential openjdk-8-jdk maven + sudo add-apt-repository ppa:timothy-downey/maven3 + sudo apt-get update + sudo apt-get install g++ build-essential openjdk-8-jdk maven3 # Make sure java8 is the selected java version sudo update-alternatives --config java sudo update-alternatives --config javac -On Windows and other operating systems, just download maven 3.x from the [official site](http://maven.apache.org/), extract it somewhere and add the `bin` directory to your `PATH` environment variable. +On Windows and other operating systems, just download maven 3.3.x from the [official site](http://maven.apache.org/), extract it somewhere and add the `bin` directory to your `PATH` environment variable. Clone this repository. If you don't have git you can download the sources as a zip file from [here](https://github.com/Athou/commafeed/archive/master.zip) From b6b1b4ebbed4ab4cedfdb061df98ad32fb37c604 Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 26 Jun 2015 14:54:14 +0200 Subject: [PATCH 224/241] fix build, 2.0.1 has been deleted (https://github.com/dlmanning/gulp-sass/issues/305) --- package.json | 2 +- pom.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 051f2856..41239ace 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "gulp-filter": "2.0.2", "gulp-connect": "2.2.0", "connect-modrewrite": "0.8.1", - "gulp-sass": "2.0.1", + "gulp-sass": "2.0.2", "gulp-useref": "1.1.2", "gulp-angular-templatecache": "1.6.0" } diff --git a/pom.xml b/pom.xml index 11c039f5..ecc0e8e1 100644 --- a/pom.xml +++ b/pom.xml @@ -141,8 +141,8 @@ generate-resources - v0.12.4 - 2.10.1 + v0.10.39 + 2.12.1 From 076594c78e53d227d7a5efdf9b93016bc4ee6214 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 29 Jun 2015 12:56:17 +0200 Subject: [PATCH 225/241] force filter expression to lowercase --- src/main/java/com/commafeed/frontend/resource/FeedREST.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/commafeed/frontend/resource/FeedREST.java b/src/main/java/com/commafeed/frontend/resource/FeedREST.java index 4029bc78..31257e9d 100644 --- a/src/main/java/com/commafeed/frontend/resource/FeedREST.java +++ b/src/main/java/com/commafeed/frontend/resource/FeedREST.java @@ -442,7 +442,7 @@ public class FeedREST { } FeedSubscription subscription = feedSubscriptionDAO.findById(user, req.getId()); - subscription.setFilter(req.getFilter()); + subscription.setFilter(StringUtils.lowerCase(req.getFilter())); if (StringUtils.isNotBlank(req.getName())) { subscription.setTitle(req.getName()); From 13ed92bb9418e2c57b90929fb22b8b75bba03f00 Mon Sep 17 00:00:00 2001 From: Athou Date: Sat, 4 Jul 2015 09:06:47 +0200 Subject: [PATCH 226/241] add maven-wrapper --- maven/maven-wrapper.jar | Bin 0 -> 71910 bytes maven/maven-wrapper.properties | 3 + mvnw | 234 +++++++++++++++++++++++++++++++++ mvnw.bat | 174 ++++++++++++++++++++++++ pom.xml | 5 + 5 files changed, 416 insertions(+) create mode 100644 maven/maven-wrapper.jar create mode 100644 maven/maven-wrapper.properties create mode 100644 mvnw create mode 100644 mvnw.bat diff --git a/maven/maven-wrapper.jar b/maven/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..18ba302c65c4f71f57e0daa0ea1b59cb85759b5c GIT binary patch literal 71910 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + local basedir=$(pwd) + local wdir=$(pwd) + while [ "$wdir" != '/' ] ; do + wdir=$(cd "$wdir/.."; pwd) + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER="org.apache.maven.wrapper.MavenWrapperMain" + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + -classpath \ +"$MAVEN_PROJECTBASEDIR/maven/maven-wrapper.jar" \ + ${WRAPPER_LAUNCHER} "$@" diff --git a/mvnw.bat b/mvnw.bat new file mode 100644 index 00000000..f8ede7fc --- /dev/null +++ b/mvnw.bat @@ -0,0 +1,174 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto chkMHome + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:chkMHome +if not "%M2_HOME%"=="" goto valMHome + +SET "M2_HOME=%~dp0.." +if not "%M2_HOME%"=="" goto valMHome + +echo. +echo Error: M2_HOME not found in your environment. >&2 +echo Please set the M2_HOME variable in your environment to match the >&2 +echo location of the Maven installation. >&2 +echo. +goto error + +:valMHome + +:stripMHome +if not "_%M2_HOME:~-1%"=="_\" goto checkMCmd +set "M2_HOME=%M2_HOME:~0,-1%" +goto stripMHome + +:checkMCmd +if exist "%M2_HOME%\bin\mvn.cmd" goto init + +echo. +echo Error: M2_HOME is set to an invalid directory. >&2 +echo M2_HOME = "%M2_HOME%" >&2 +echo Please set the M2_HOME variable in your environment to match the >&2 +echo location of the Maven installation >&2 +echo. +goto error +@REM ==== END VALIDATION ==== + +:init + +set MAVEN_CMD_LINE_ARGS=%* + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\maven\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% + +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml index ecc0e8e1..5b75ecbb 100644 --- a/pom.xml +++ b/pom.xml @@ -182,6 +182,11 @@ + + com.rimerosolutions.maven.plugins + wrapper-maven-plugin + 0.0.4 + From 737cec744aa1bafa857e95af735730ca75e42f41 Mon Sep 17 00:00:00 2001 From: Athou Date: Sat, 4 Jul 2015 17:02:23 +0200 Subject: [PATCH 227/241] fix mvnw on windows --- mvnw.bat | 37 ++----------------------------------- 1 file changed, 2 insertions(+), 35 deletions(-) diff --git a/mvnw.bat b/mvnw.bat index f8ede7fc..b3251bce 100644 --- a/mvnw.bat +++ b/mvnw.bat @@ -24,7 +24,6 @@ @REM JAVA_HOME - location of a JDK home dir @REM @REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @@ -66,7 +65,7 @@ echo. goto error :OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto chkMHome +if exist "%JAVA_HOME%\bin\java.exe" goto init echo. echo Error: JAVA_HOME is set to an invalid directory. >&2 @@ -76,38 +75,6 @@ echo location of your Java installation. >&2 echo. goto error -:chkMHome -if not "%M2_HOME%"=="" goto valMHome - -SET "M2_HOME=%~dp0.." -if not "%M2_HOME%"=="" goto valMHome - -echo. -echo Error: M2_HOME not found in your environment. >&2 -echo Please set the M2_HOME variable in your environment to match the >&2 -echo location of the Maven installation. >&2 -echo. -goto error - -:valMHome - -:stripMHome -if not "_%M2_HOME:~-1%"=="_\" goto checkMCmd -set "M2_HOME=%M2_HOME:~0,-1%" -goto stripMHome - -:checkMCmd -if exist "%M2_HOME%\bin\mvn.cmd" goto init - -echo. -echo Error: M2_HOME is set to an invalid directory. >&2 -echo M2_HOME = "%M2_HOME%" >&2 -echo Please set the M2_HOME variable in your environment to match the >&2 -echo location of the Maven installation >&2 -echo. -goto error -@REM ==== END VALIDATION ==== - :init set MAVEN_CMD_LINE_ARGS=%* @@ -149,7 +116,7 @@ for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do s SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\maven\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% %WRAPPER_LAUNCHER% -Dmaven.multiModuleProjectDirectory=" %MAVEN_CMD_LINE_ARGS% if ERRORLEVEL 1 goto error goto end From 96c09bf4cdaae8692ef04da324c2b8aecb18ef20 Mon Sep 17 00:00:00 2001 From: Athou Date: Sat, 4 Jul 2015 17:05:03 +0200 Subject: [PATCH 228/241] ending quote missing --- mvnw.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mvnw.bat b/mvnw.bat index b3251bce..922fc372 100644 --- a/mvnw.bat +++ b/mvnw.bat @@ -116,7 +116,7 @@ for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do s SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\maven\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% %WRAPPER_LAUNCHER% -Dmaven.multiModuleProjectDirectory=" %MAVEN_CMD_LINE_ARGS% +%MAVEN_JAVA_EXE% -Dmaven.multiModuleProjectDirectory="" %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% if ERRORLEVEL 1 goto error goto end From 9b14ffa14c86ec8ac3955fcd693626dd3265e86d Mon Sep 17 00:00:00 2001 From: Athou Date: Sat, 4 Jul 2015 17:08:53 +0200 Subject: [PATCH 229/241] update readme to use maven wrapper --- README.md | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index fff390ae..099d9ac0 100644 --- a/README.md +++ b/README.md @@ -21,34 +21,30 @@ Browser extensions: [Chrome](https://github.com/Athou/commafeed-chrome) - [Firef vi config.yml java -Djava.net.preferIPv4Stack=true -jar commafeed.jar server config.yml -### The short version +### The short version (build from sources) git clone https://github.com/Athou/commafeed.git cd commafeed - mvn clean package + ./mvnw clean package cp config.yml.example config.yml vi config.yml java -Djava.net.preferIPv4Stack=true -jar target/commafeed.jar server config.yml -### The long version +### The long version (same as the short version, but more detailed) CommaFeed 2.0 has been rewritten to use Dropwizard and gulp instead of using tomee and wro4j. The latest version of the 1.x branch is available [here](https://github.com/Athou/commafeed/tree/1.x). For storage, you can either use an embedded H2 database (use it only to test CommaFeed) or an external MySQL, PostgreSQL or SQLServer database. -You also need Maven 3.x (and a Java 1.8+ JDK) installed in order to build the application. +You also need the Java 1.8+ JDK in order to build the application. -To install maven and openjdk on Ubuntu, issue the following commands +To install the required packages to build CommaFeed on Ubuntu, issue the following commands - sudo add-apt-repository ppa:timothy-downey/maven3 - sudo apt-get update - sudo apt-get install g++ build-essential openjdk-8-jdk maven3 + sudo apt-get install g++ build-essential openjdk-8-jdk # Make sure java8 is the selected java version sudo update-alternatives --config java sudo update-alternatives --config javac -On Windows and other operating systems, just download maven 3.3.x from the [official site](http://maven.apache.org/), extract it somewhere and add the `bin` directory to your `PATH` environment variable. - Clone this repository. If you don't have git you can download the sources as a zip file from [here](https://github.com/Athou/commafeed/archive/master.zip) git clone https://github.com/Athou/commafeed.git @@ -56,7 +52,7 @@ Clone this repository. If you don't have git you can download the sources as a z Now build the application - mvn clean package + ./mvnw clean package Copy `config.yml.example` to `config.yml` then edit the file to your liking. Issue the following command to run the app, the server will listen by default on `http://localhost:8082`. The default user is `admin` and the default password is `admin`. From 58c1650863dc1f41a35a14763138f2dcb3252ac8 Mon Sep 17 00:00:00 2001 From: Athou Date: Sun, 5 Jul 2015 14:10:04 +0200 Subject: [PATCH 230/241] make mvnw executable --- mvnw | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 mvnw diff --git a/mvnw b/mvnw old mode 100644 new mode 100755 From 35e02f9d98c3a95e1620f788b22857ac291731fb Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 9 Jul 2015 12:34:54 +0200 Subject: [PATCH 231/241] querydsl upgrade --- pom.xml | 6 ++-- .../backend/dao/FeedCategoryDAO.java | 10 +++---- .../com/commafeed/backend/dao/FeedDAO.java | 19 +++++++------ .../backend/dao/FeedEntryContentDAO.java | 15 +++++----- .../commafeed/backend/dao/FeedEntryDAO.java | 24 ++++++++-------- .../backend/dao/FeedEntryStatusDAO.java | 28 +++++++++---------- .../backend/dao/FeedEntryTagDAO.java | 4 +-- .../backend/dao/FeedSubscriptionDAO.java | 18 ++++++------ .../com/commafeed/backend/dao/GenericDAO.java | 13 +++++---- .../com/commafeed/backend/dao/UserDAO.java | 8 +++--- .../commafeed/backend/dao/UserRoleDAO.java | 4 +-- .../backend/dao/UserSettingsDAO.java | 2 +- 12 files changed, 77 insertions(+), 74 deletions(-) diff --git a/pom.xml b/pom.xml index 5b75ecbb..385cf1cf 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ 1.8 0.9.0-rc1 4.0 - 3.6.4 + 4.0.2 1.5.0 @@ -265,14 +265,14 @@ - com.mysema.querydsl + com.querydsl querydsl-apt ${querydsl.version} provided hibernate - com.mysema.querydsl + com.querydsl querydsl-jpa ${querydsl.version} diff --git a/src/main/java/com/commafeed/backend/dao/FeedCategoryDAO.java b/src/main/java/com/commafeed/backend/dao/FeedCategoryDAO.java index 3e788974..6fc78806 100644 --- a/src/main/java/com/commafeed/backend/dao/FeedCategoryDAO.java +++ b/src/main/java/com/commafeed/backend/dao/FeedCategoryDAO.java @@ -13,7 +13,7 @@ import com.commafeed.backend.model.FeedCategory; import com.commafeed.backend.model.QFeedCategory; import com.commafeed.backend.model.QUser; import com.commafeed.backend.model.User; -import com.mysema.query.types.Predicate; +import com.querydsl.core.types.Predicate; @Singleton public class FeedCategoryDAO extends GenericDAO { @@ -26,11 +26,11 @@ public class FeedCategoryDAO extends GenericDAO { } public List findAll(User user) { - return newQuery().from(category).where(category.user.eq(user)).join(category.user, QUser.user).fetch().list(category); + return query().selectFrom(category).where(category.user.eq(user)).join(category.user, QUser.user).fetchJoin().fetch(); } public FeedCategory findById(User user, Long id) { - return newQuery().from(category).where(category.user.eq(user), category.id.eq(id)).uniqueResult(category); + return query().selectFrom(category).where(category.user.eq(user), category.id.eq(id)).fetchOne(); } public FeedCategory findByName(User user, String name, FeedCategory parent) { @@ -40,7 +40,7 @@ public class FeedCategoryDAO extends GenericDAO { } else { parentPredicate = category.parent.eq(parent); } - return newQuery().from(category).where(category.user.eq(user), category.name.eq(name), parentPredicate).uniqueResult(category); + return query().selectFrom(category).where(category.user.eq(user), category.name.eq(name), parentPredicate).fetchOne(); } public List findByParent(User user, FeedCategory parent) { @@ -50,7 +50,7 @@ public class FeedCategoryDAO extends GenericDAO { } else { parentPredicate = category.parent.eq(parent); } - return newQuery().from(category).where(category.user.eq(user), parentPredicate).list(category); + return query().selectFrom(category).where(category.user.eq(user), parentPredicate).fetch(); } public List findAllChildrenCategories(User user, FeedCategory parent) { diff --git a/src/main/java/com/commafeed/backend/dao/FeedDAO.java b/src/main/java/com/commafeed/backend/dao/FeedDAO.java index e522437c..67f86cb8 100644 --- a/src/main/java/com/commafeed/backend/dao/FeedDAO.java +++ b/src/main/java/com/commafeed/backend/dao/FeedDAO.java @@ -15,8 +15,9 @@ import com.commafeed.backend.model.QFeed; import com.commafeed.backend.model.QFeedSubscription; import com.commafeed.backend.model.QUser; import com.google.common.collect.Iterables; -import com.mysema.query.jpa.hibernate.HibernateQuery; -import com.mysema.query.jpa.hibernate.HibernateSubQuery; +import com.querydsl.jpa.JPAExpressions; +import com.querydsl.jpa.JPQLQuery; +import com.querydsl.jpa.hibernate.HibernateQuery; @Singleton public class FeedDAO extends GenericDAO { @@ -29,23 +30,23 @@ public class FeedDAO extends GenericDAO { } public List findNextUpdatable(int count, Date lastLoginThreshold) { - HibernateQuery query = newQuery().from(feed); + HibernateQuery query = query().selectFrom(feed); query.where(feed.disabledUntil.isNull().or(feed.disabledUntil.lt(new Date()))); if (lastLoginThreshold != null) { QFeedSubscription subs = QFeedSubscription.feedSubscription; QUser user = QUser.user; - HibernateSubQuery subQuery = new HibernateSubQuery().from(subs); + JPQLQuery subQuery = JPAExpressions.selectOne().from(subs); subQuery.join(subs.user, user).where(user.lastLogin.gt(lastLoginThreshold)); query.where(subQuery.exists()); } - return query.orderBy(feed.disabledUntil.asc()).limit(count).distinct().list(feed); + return query.orderBy(feed.disabledUntil.asc()).limit(count).distinct().fetch(); } public Feed findByUrl(String normalizedUrl) { - List feeds = newQuery().from(feed).where(feed.normalizedUrlHash.eq(DigestUtils.sha1Hex(normalizedUrl))).list(feed); + List feeds = query().selectFrom(feed).where(feed.normalizedUrlHash.eq(DigestUtils.sha1Hex(normalizedUrl))).fetch(); Feed feed = Iterables.getFirst(feeds, null); if (feed != null && StringUtils.equals(normalizedUrl, feed.getNormalizedUrl())) { return feed; @@ -54,12 +55,12 @@ public class FeedDAO extends GenericDAO { } public List findByTopic(String topic) { - return newQuery().from(feed).where(feed.pushTopicHash.eq(DigestUtils.sha1Hex(topic))).list(feed); + return query().selectFrom(feed).where(feed.pushTopicHash.eq(DigestUtils.sha1Hex(topic))).fetch(); } public List findWithoutSubscriptions(int max) { QFeedSubscription sub = QFeedSubscription.feedSubscription; - return newQuery().from(feed).where(new HibernateSubQuery().from(sub).where(sub.feed.eq(feed)).notExists()).limit(max).list(feed); - // return newQuery().from(feed).leftJoin(feed.subscriptions, sub).where(sub.id.isNull()).limit(max).list(feed); + return query().selectFrom(feed).where(JPAExpressions.selectOne().from(sub).where(sub.feed.eq(feed)).notExists()).limit(max) + .fetch(); } } diff --git a/src/main/java/com/commafeed/backend/dao/FeedEntryContentDAO.java b/src/main/java/com/commafeed/backend/dao/FeedEntryContentDAO.java index 5056e7f5..68eeb41e 100644 --- a/src/main/java/com/commafeed/backend/dao/FeedEntryContentDAO.java +++ b/src/main/java/com/commafeed/backend/dao/FeedEntryContentDAO.java @@ -10,13 +10,14 @@ import org.hibernate.SessionFactory; import com.commafeed.backend.model.FeedEntryContent; import com.commafeed.backend.model.QFeedEntry; import com.commafeed.backend.model.QFeedEntryContent; -import com.google.common.collect.Iterables; -import com.mysema.query.jpa.hibernate.HibernateSubQuery; +import com.querydsl.jpa.JPAExpressions; +import com.querydsl.jpa.JPQLQuery; @Singleton public class FeedEntryContentDAO extends GenericDAO { private QFeedEntryContent content = QFeedEntryContent.feedEntryContent; + private QFeedEntry entry = QFeedEntry.feedEntry; @Inject public FeedEntryContentDAO(SessionFactory sessionFactory) { @@ -24,16 +25,14 @@ public class FeedEntryContentDAO extends GenericDAO { } public Long findExisting(String contentHash, String titleHash) { - List list = newQuery().from(content).where(content.contentHash.eq(contentHash), content.titleHash.eq(titleHash)).limit(1) - .list(content.id); - return Iterables.getFirst(list, null); + return query().select(content.id).from(content).where(content.contentHash.eq(contentHash), content.titleHash.eq(titleHash)) + .fetchFirst(); } public int deleteWithoutEntries(int max) { - QFeedEntry entry = QFeedEntry.feedEntry; - HibernateSubQuery subQuery = new HibernateSubQuery().from(entry).where(entry.content.id.eq(content.id)); - List list = newQuery().from(content).where(subQuery.notExists()).limit(max).list(content); + JPQLQuery subQuery = JPAExpressions.selectOne().from(entry).where(entry.content.id.eq(content.id)); + List list = query().selectFrom(content).where(subQuery.notExists()).limit(max).fetch(); int deleted = list.size(); delete(list); diff --git a/src/main/java/com/commafeed/backend/dao/FeedEntryDAO.java b/src/main/java/com/commafeed/backend/dao/FeedEntryDAO.java index a5a13790..bbb3853f 100644 --- a/src/main/java/com/commafeed/backend/dao/FeedEntryDAO.java +++ b/src/main/java/com/commafeed/backend/dao/FeedEntryDAO.java @@ -6,18 +6,17 @@ import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Singleton; -import lombok.AllArgsConstructor; -import lombok.Getter; - import org.apache.commons.codec.digest.DigestUtils; import org.hibernate.SessionFactory; import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.QFeedEntry; -import com.google.common.collect.Iterables; -import com.mysema.query.Tuple; -import com.mysema.query.types.expr.NumberExpression; +import com.querydsl.core.Tuple; +import com.querydsl.core.types.dsl.NumberExpression; + +import lombok.AllArgsConstructor; +import lombok.Getter; @Singleton public class FeedEntryDAO extends GenericDAO { @@ -30,24 +29,25 @@ public class FeedEntryDAO extends GenericDAO { } public Long findExisting(String guid, Feed feed) { - List list = newQuery().from(entry).where(entry.guidHash.eq(DigestUtils.sha1Hex(guid)), entry.feed.eq(feed)).limit(1) - .list(entry.id); - return Iterables.getFirst(list, null); + return query().select(entry.id).from(entry).where(entry.guidHash.eq(DigestUtils.sha1Hex(guid)), entry.feed.eq(feed)).limit(1) + .fetchOne(); } public List findFeedsExceedingCapacity(long maxCapacity, long max) { NumberExpression count = entry.id.count(); - List tuples = newQuery().from(entry).groupBy(entry.feed).having(count.gt(maxCapacity)).limit(max).list(entry.feed.id, count); + List tuples = query().select(entry.feed.id, count).from(entry).groupBy(entry.feed).having(count.gt(maxCapacity)).limit(max) + .fetch(); return tuples.stream().map(t -> new FeedCapacity(t.get(entry.feed.id), t.get(count))).collect(Collectors.toList()); } public int delete(Long feedId, long max) { - List list = newQuery().from(entry).where(entry.feed.id.eq(feedId)).limit(max).list(entry); + + List list = query().selectFrom(entry).where(entry.feed.id.eq(feedId)).limit(max).fetch(); return delete(list); } public int deleteOldEntries(Long feedId, long max) { - List list = newQuery().from(entry).where(entry.feed.id.eq(feedId)).orderBy(entry.updated.asc()).limit(max).list(entry); + List list = query().selectFrom(entry).where(entry.feed.id.eq(feedId)).orderBy(entry.updated.asc()).limit(max).fetch(); return delete(list); } diff --git a/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java b/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java index 524012dd..7524b816 100644 --- a/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java +++ b/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java @@ -30,9 +30,9 @@ import com.commafeed.backend.model.UserSettings.ReadingOrder; import com.commafeed.frontend.model.UnreadCount; import com.google.common.collect.Iterables; import com.google.common.collect.Ordering; -import com.mysema.query.BooleanBuilder; -import com.mysema.query.Tuple; -import com.mysema.query.jpa.hibernate.HibernateQuery; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.Tuple; +import com.querydsl.jpa.hibernate.HibernateQuery; @Singleton public class FeedEntryStatusDAO extends GenericDAO { @@ -68,7 +68,7 @@ public class FeedEntryStatusDAO extends GenericDAO { private static final Comparator STATUS_COMPARATOR_ASC = Ordering.from(STATUS_COMPARATOR_DESC).reverse(); public FeedEntryStatus getStatus(User user, FeedSubscription sub, FeedEntry entry) { - List statuses = newQuery().from(status).where(status.entry.eq(entry), status.subscription.eq(sub)).list(status); + List statuses = query().selectFrom(status).where(status.entry.eq(entry), status.subscription.eq(sub)).fetch(); FeedEntryStatus status = Iterables.getFirst(statuses, null); return handleStatus(user, status, sub, entry); } @@ -93,7 +93,7 @@ public class FeedEntryStatusDAO extends GenericDAO { } public List findStarred(User user, Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent) { - HibernateQuery query = newQuery().from(status).where(status.user.eq(user), status.starred.isTrue()); + HibernateQuery query = query().selectFrom(status).where(status.user.eq(user), status.starred.isTrue()); if (newerThan != null) { query.where(status.entryInserted.gt(newerThan)); } @@ -110,7 +110,7 @@ public class FeedEntryStatusDAO extends GenericDAO { query.setTimeout(timeout / 1000); } - List statuses = query.list(status); + List statuses = query.fetch(); for (FeedEntryStatus status : statuses) { status = handleStatus(user, status, status.getSubscription(), status.getEntry()); fetchTags(user, status); @@ -118,10 +118,10 @@ public class FeedEntryStatusDAO extends GenericDAO { return lazyLoadContent(includeContent, statuses); } - private HibernateQuery buildQuery(User user, FeedSubscription sub, boolean unreadOnly, List keywords, Date newerThan, - int offset, int limit, ReadingOrder order, Date last, String tag) { + private HibernateQuery buildQuery(User user, FeedSubscription sub, boolean unreadOnly, List keywords, + Date newerThan, int offset, int limit, ReadingOrder order, Date last, String tag) { - HibernateQuery query = newQuery().from(entry).where(entry.feed.eq(sub.getFeed())); + HibernateQuery query = query().selectFrom(entry).where(entry.feed.eq(sub.getFeed())); if (CollectionUtils.isNotEmpty(keywords)) { query.join(entry.content, content); @@ -197,8 +197,8 @@ public class FeedEntryStatusDAO extends GenericDAO { FixedSizeSortedSet set = new FixedSizeSortedSet(capacity, comparator); for (FeedSubscription sub : subs) { Date last = (order != null && set.isFull()) ? set.last().getEntryUpdated() : null; - HibernateQuery query = buildQuery(user, sub, unreadOnly, keywords, newerThan, -1, capacity, order, last, tag); - List tuples = query.list(entry.id, entry.updated, status.id); + HibernateQuery query = buildQuery(user, sub, unreadOnly, keywords, newerThan, -1, capacity, order, last, tag); + List tuples = query.select(entry.id, entry.updated, status.id).fetch(); for (Tuple tuple : tuples) { Long id = tuple.get(entry.id); Date updated = tuple.get(entry.updated); @@ -245,8 +245,8 @@ public class FeedEntryStatusDAO extends GenericDAO { public UnreadCount getUnreadCount(User user, FeedSubscription subscription) { UnreadCount uc = null; - HibernateQuery query = buildQuery(user, subscription, true, null, null, -1, -1, null, null, null); - List tuples = query.list(entry.count(), entry.updated.max()); + HibernateQuery query = buildQuery(user, subscription, true, null, null, -1, -1, null, null, null); + List tuples = query.select(entry.count(), entry.updated.max()).fetch(); for (Tuple tuple : tuples) { Long count = tuple.get(entry.count()); Date updated = tuple.get(entry.updated.max()); @@ -266,7 +266,7 @@ public class FeedEntryStatusDAO extends GenericDAO { } public List getOldStatuses(Date olderThan, int limit) { - return newQuery().from(status).where(status.entryInserted.lt(olderThan), status.starred.isFalse()).limit(limit).list(status); + return query().selectFrom(status).where(status.entryInserted.lt(olderThan), status.starred.isFalse()).limit(limit).fetch(); } } diff --git a/src/main/java/com/commafeed/backend/dao/FeedEntryTagDAO.java b/src/main/java/com/commafeed/backend/dao/FeedEntryTagDAO.java index e2dc3ac9..f0ef9388 100644 --- a/src/main/java/com/commafeed/backend/dao/FeedEntryTagDAO.java +++ b/src/main/java/com/commafeed/backend/dao/FeedEntryTagDAO.java @@ -23,10 +23,10 @@ public class FeedEntryTagDAO extends GenericDAO { } public List findByUser(User user) { - return newQuery().from(tag).where(tag.user.eq(user)).distinct().list(tag.name); + return query().selectDistinct(tag.name).from(tag).where(tag.user.eq(user)).fetch(); } public List findByEntry(User user, FeedEntry entry) { - return newQuery().from(tag).where(tag.user.eq(user), tag.entry.eq(entry)).list(tag); + return query().selectFrom(tag).where(tag.user.eq(user), tag.entry.eq(entry)).fetch(); } } diff --git a/src/main/java/com/commafeed/backend/dao/FeedSubscriptionDAO.java b/src/main/java/com/commafeed/backend/dao/FeedSubscriptionDAO.java index bcda4c1e..bfdc39e0 100644 --- a/src/main/java/com/commafeed/backend/dao/FeedSubscriptionDAO.java +++ b/src/main/java/com/commafeed/backend/dao/FeedSubscriptionDAO.java @@ -16,7 +16,7 @@ import com.commafeed.backend.model.Models; import com.commafeed.backend.model.QFeedSubscription; import com.commafeed.backend.model.User; import com.google.common.collect.Iterables; -import com.mysema.query.jpa.hibernate.HibernateQuery; +import com.querydsl.jpa.hibernate.HibernateQuery; @Singleton public class FeedSubscriptionDAO extends GenericDAO { @@ -29,34 +29,34 @@ public class FeedSubscriptionDAO extends GenericDAO { } public FeedSubscription findById(User user, Long id) { - List subs = newQuery().from(sub).where(sub.user.eq(user), sub.id.eq(id)).leftJoin(sub.feed).fetch() - .leftJoin(sub.category).fetch().list(sub); + List subs = query().selectFrom(sub).where(sub.user.eq(user), sub.id.eq(id)).leftJoin(sub.feed).fetchJoin() + .leftJoin(sub.category).fetchJoin().fetch(); return initRelations(Iterables.getFirst(subs, null)); } public List findByFeed(Feed feed) { - return newQuery().from(sub).where(sub.feed.eq(feed)).list(sub); + return query().selectFrom(sub).where(sub.feed.eq(feed)).fetch(); } public FeedSubscription findByFeed(User user, Feed feed) { - List subs = newQuery().from(sub).where(sub.user.eq(user), sub.feed.eq(feed)).list(sub); + List subs = query().selectFrom(sub).where(sub.user.eq(user), sub.feed.eq(feed)).fetch(); return initRelations(Iterables.getFirst(subs, null)); } public List findAll(User user) { - List subs = newQuery().from(sub).where(sub.user.eq(user)).leftJoin(sub.feed).fetch().leftJoin(sub.category) - .fetch().list(sub); + List subs = query().selectFrom(sub).where(sub.user.eq(user)).leftJoin(sub.feed).fetchJoin() + .leftJoin(sub.category).fetchJoin().fetch(); return initRelations(subs); } public List findByCategory(User user, FeedCategory category) { - HibernateQuery query = newQuery().from(sub).where(sub.user.eq(user)); + HibernateQuery query = query().selectFrom(sub).where(sub.user.eq(user)); if (category == null) { query.where(sub.category.isNull()); } else { query.where(sub.category.eq(category)); } - return initRelations(query.list(sub)); + return initRelations(query.fetch()); } public List findByCategories(User user, List categories) { diff --git a/src/main/java/com/commafeed/backend/dao/GenericDAO.java b/src/main/java/com/commafeed/backend/dao/GenericDAO.java index db2a61ae..3a6884ad 100644 --- a/src/main/java/com/commafeed/backend/dao/GenericDAO.java +++ b/src/main/java/com/commafeed/backend/dao/GenericDAO.java @@ -1,22 +1,25 @@ package com.commafeed.backend.dao; -import io.dropwizard.hibernate.AbstractDAO; - import java.util.Collection; import org.hibernate.SessionFactory; import com.commafeed.backend.model.AbstractModel; -import com.mysema.query.jpa.hibernate.HibernateQuery; +import com.querydsl.jpa.hibernate.HibernateQueryFactory; + +import io.dropwizard.hibernate.AbstractDAO; public abstract class GenericDAO extends AbstractDAO { + private HibernateQueryFactory factory; + protected GenericDAO(SessionFactory sessionFactory) { super(sessionFactory); + this.factory = new HibernateQueryFactory(() -> currentSession()); } - protected HibernateQuery newQuery() { - return new HibernateQuery(currentSession()); + protected HibernateQueryFactory query() { + return factory; } public void saveOrUpdate(T model) { diff --git a/src/main/java/com/commafeed/backend/dao/UserDAO.java b/src/main/java/com/commafeed/backend/dao/UserDAO.java index 3bc94cb2..8b51e95c 100644 --- a/src/main/java/com/commafeed/backend/dao/UserDAO.java +++ b/src/main/java/com/commafeed/backend/dao/UserDAO.java @@ -19,18 +19,18 @@ public class UserDAO extends GenericDAO { } public User findByName(String name) { - return newQuery().from(user).where(user.name.equalsIgnoreCase(name)).uniqueResult(user); + return query().selectFrom(user).where(user.name.equalsIgnoreCase(name)).fetchOne(); } public User findByApiKey(String key) { - return newQuery().from(user).where(user.apiKey.equalsIgnoreCase(key)).uniqueResult(user); + return query().selectFrom(user).where(user.apiKey.equalsIgnoreCase(key)).fetchOne(); } public User findByEmail(String email) { - return newQuery().from(user).where(user.email.equalsIgnoreCase(email)).uniqueResult(user); + return query().selectFrom(user).where(user.email.equalsIgnoreCase(email)).fetchOne(); } public long count() { - return newQuery().from(user).count(); + return query().selectFrom(user).fetchCount(); } } diff --git a/src/main/java/com/commafeed/backend/dao/UserRoleDAO.java b/src/main/java/com/commafeed/backend/dao/UserRoleDAO.java index 3c149499..303d454e 100644 --- a/src/main/java/com/commafeed/backend/dao/UserRoleDAO.java +++ b/src/main/java/com/commafeed/backend/dao/UserRoleDAO.java @@ -25,11 +25,11 @@ public class UserRoleDAO extends GenericDAO { } public List findAll() { - return newQuery().from(role).leftJoin(role.user).fetch().distinct().list(role); + return query().selectFrom(role).leftJoin(role.user).fetchJoin().distinct().fetch(); } public List findAll(User user) { - return newQuery().from(role).where(role.user.eq(user)).distinct().list(role); + return query().selectFrom(role).where(role.user.eq(user)).distinct().fetch(); } public Set findRoles(User user) { diff --git a/src/main/java/com/commafeed/backend/dao/UserSettingsDAO.java b/src/main/java/com/commafeed/backend/dao/UserSettingsDAO.java index 92e8afd5..0553ef84 100644 --- a/src/main/java/com/commafeed/backend/dao/UserSettingsDAO.java +++ b/src/main/java/com/commafeed/backend/dao/UserSettingsDAO.java @@ -20,6 +20,6 @@ public class UserSettingsDAO extends GenericDAO { } public UserSettings findByUser(User user) { - return newQuery().from(settings).where(settings.user.eq(user)).uniqueResult(settings); + return query().selectFrom(settings).where(settings.user.eq(user)).fetchFirst(); } } From 6c61d47d785cb022907df5ab6b5e7a908bc47029 Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 9 Jul 2015 16:03:38 +0200 Subject: [PATCH 232/241] swagger.json no longer generated at runtime --- bower.json | 2 +- gulpfile.js | 3 +- pom.xml | 43 +++++++++++++------ src/main/app/api/index.html | 2 +- .../com/commafeed/CommaFeedApplication.java | 43 +++++-------------- .../commafeed/frontend/model/Category.java | 5 +-- .../com/commafeed/frontend/model/Entries.java | 5 +-- .../com/commafeed/frontend/model/Entry.java | 8 ++-- .../commafeed/frontend/model/FeedInfo.java | 3 +- .../commafeed/frontend/model/ServerInfo.java | 3 +- .../commafeed/frontend/model/Settings.java | 5 +-- .../frontend/model/Subscription.java | 8 ++-- .../commafeed/frontend/model/UnreadCount.java | 3 +- .../commafeed/frontend/model/UserModel.java | 5 +-- .../model/request/AddCategoryRequest.java | 5 +-- .../request/CategoryModificationRequest.java | 5 +-- .../model/request/CollapseRequest.java | 5 +-- .../model/request/FeedInfoRequest.java | 5 +-- .../model/request/FeedMergeRequest.java | 5 +-- .../request/FeedModificationRequest.java | 5 +-- .../frontend/model/request/IDRequest.java | 5 +-- .../frontend/model/request/LoginRequest.java | 5 +-- .../frontend/model/request/MarkRequest.java | 5 +-- .../model/request/MultipleMarkRequest.java | 5 +-- .../model/request/PasswordResetRequest.java | 7 ++- .../request/ProfileModificationRequest.java | 5 +-- .../model/request/RegistrationRequest.java | 7 ++- .../frontend/model/request/StarRequest.java | 5 +-- .../model/request/SubscribeRequest.java | 5 +-- .../frontend/model/request/TagRequest.java | 5 +-- .../frontend/resource/AdminREST.java | 13 +++--- .../frontend/resource/CategoryREST.java | 15 +++---- .../frontend/resource/EntryREST.java | 13 +++--- .../commafeed/frontend/resource/FeedREST.java | 15 +++---- .../resource/PubSubHubbubCallbackREST.java | 9 ++-- .../frontend/resource/ServerREST.java | 11 +++-- .../commafeed/frontend/resource/UserREST.java | 17 ++++---- 37 files changed, 142 insertions(+), 173 deletions(-) diff --git a/bower.json b/bower.json index 0ab5de15..7b2fb50a 100644 --- a/bower.json +++ b/bower.json @@ -28,7 +28,7 @@ "devicejs": "0.2.4", "readabilicons": "arc90/readability-readabilicons#34c55561c5b8ec6e90714b50237c06b13cb9d59c", "zocial-less": "1.0.0", - "swagger-ui": "2.1.8-M1" + "swagger-ui": "2.1.0" }, "resolutions": { "angular": "1.3.14", diff --git a/gulpfile.js b/gulpfile.js index d9d3f736..c3d90b0b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -49,8 +49,9 @@ gulp.task('select2', function() { gulp.task('swagger-ui', function() { var index_html = SRC_DIR + 'api/index.html'; + var swagger_json = 'target/swagger/swagger.json'; var lib = SRC_DIR + 'lib/swagger-ui/dist/**/*'; - return gulp.src([lib, index_html]).pipe(gulp.dest(BUILD_DIR + 'api')); + return gulp.src([lib, index_html, swagger_json]).pipe(gulp.dest(BUILD_DIR + 'api')); }); gulp.task('template-cache', function() { diff --git a/pom.xml b/pom.xml index 385cf1cf..5a9cda68 100644 --- a/pom.xml +++ b/pom.xml @@ -183,9 +183,34 @@ - com.rimerosolutions.maven.plugins - wrapper-maven-plugin - 0.0.4 + com.github.kongchen + swagger-maven-plugin + 3.1.0 + + + + com.commafeed.frontend.resource;com.commafeed.frontend.model;com.commafeed.frontend.model.request + target/swagger + /rest + + CommaFeed + ${project.version} + + + + + + + + + + + compile + + generate + + + @@ -253,15 +278,9 @@ - com.wordnik - swagger-jaxrs - 1.5.3-M1 - - - javax.ws.rs - jsr311-api - - + io.swagger + swagger-annotations + 1.5.0 diff --git a/src/main/app/api/index.html b/src/main/app/api/index.html index b78abbcf..e78dbfa7 100644 --- a/src/main/app/api/index.html +++ b/src/main/app/api/index.html @@ -25,7 +25,7 @@