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(); }