search is now context aware (will only search in selected category or feed)

This commit is contained in:
Athou
2013-08-07 15:26:43 +02:00
parent 701a1903ba
commit 4520ef4078
6 changed files with 85 additions and 109 deletions

View File

@@ -14,6 +14,7 @@ import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root; import javax.persistence.criteria.Root;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
import org.hibernate.Criteria; import org.hibernate.Criteria;
import org.hibernate.criterion.Disjunction; import org.hibernate.criterion.Disjunction;
@@ -131,7 +132,7 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
if (keywords != null) { if (keywords != null) {
Criteria contentJoin = criteria.createCriteria(FeedEntry_.content.getName(), "content", JoinType.INNER_JOIN); Criteria contentJoin = criteria.createCriteria(FeedEntry_.content.getName(), "content", JoinType.INNER_JOIN);
for (String keyword : keywords.split(" ")) { for (String keyword : StringUtils.split(keywords)) {
Disjunction or = Restrictions.disjunction(); Disjunction or = Restrictions.disjunction();
or.add(Restrictions.ilike(FeedEntryContent_.content.getName(), keyword, MatchMode.ANYWHERE)); or.add(Restrictions.ilike(FeedEntryContent_.content.getName(), keyword, MatchMode.ANYWHERE));
or.add(Restrictions.ilike(FeedEntryContent_.title.getName(), keyword, MatchMode.ANYWHERE)); or.add(Restrictions.ilike(FeedEntryContent_.title.getName(), keyword, MatchMode.ANYWHERE));

View File

@@ -5,6 +5,7 @@ import java.io.StringReader;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -29,6 +30,7 @@ import org.w3c.dom.css.CSSStyleDeclaration;
import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedSubscription; import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.frontend.model.Entry;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.gwt.i18n.client.HasDirection.Direction; import com.google.gwt.i18n.client.HasDirection.Direction;
import com.google.gwt.i18n.shared.BidiUtils; import com.google.gwt.i18n.shared.BidiUtils;
@@ -447,4 +449,27 @@ public class FeedUtils {
return rot13(new String(Base64.decodeBase64(code))); return rot13(new String(Base64.decodeBase64(code)));
} }
public static void removeUnwantedFromSearch(List<Entry> entries, String keywords) {
if (StringUtils.isBlank(keywords)) {
return;
}
Iterator<Entry> it = entries.iterator();
while (it.hasNext()) {
Entry entry = it.next();
boolean keep = true;
for (String keyword : keywords.split(" ")) {
String title = Jsoup.parse(entry.getTitle()).text();
String content = Jsoup.parse(entry.getContent()).text();
if (!StringUtils.containsIgnoreCase(content, keyword) && !StringUtils.containsIgnoreCase(title, keyword)) {
keep = false;
break;
}
}
if (!keep) {
it.remove();
}
}
}
} }

View File

@@ -27,6 +27,7 @@ import com.commafeed.backend.cache.CacheService;
import com.commafeed.backend.dao.FeedCategoryDAO; import com.commafeed.backend.dao.FeedCategoryDAO;
import com.commafeed.backend.dao.FeedEntryStatusDAO; import com.commafeed.backend.dao.FeedEntryStatusDAO;
import com.commafeed.backend.dao.FeedSubscriptionDAO; import com.commafeed.backend.dao.FeedSubscriptionDAO;
import com.commafeed.backend.feeds.FeedUtils;
import com.commafeed.backend.model.FeedCategory; import com.commafeed.backend.model.FeedCategory;
import com.commafeed.backend.model.FeedEntryStatus; import com.commafeed.backend.model.FeedEntryStatus;
import com.commafeed.backend.model.FeedSubscription; import com.commafeed.backend.model.FeedSubscription;
@@ -84,7 +85,7 @@ public class CategoryREST extends AbstractREST {
@Inject @Inject
CacheService cache; CacheService cache;
@Inject @Inject
ApplicationSettingsService applicationSettingsService; ApplicationSettingsService applicationSettingsService;
@@ -103,9 +104,15 @@ public class CategoryREST extends AbstractREST {
@ApiParam(value = "offset for paging") @DefaultValue("0") @QueryParam("offset") int offset, @ApiParam( @ApiParam(value = "offset for paging") @DefaultValue("0") @QueryParam("offset") int offset, @ApiParam(
value = "limit for paging, default 20, maximum 50") @DefaultValue("20") @QueryParam("limit") int limit, @ApiParam( value = "limit for paging, default 20, maximum 50") @DefaultValue("20") @QueryParam("limit") int limit, @ApiParam(
value = "date ordering", value = "date ordering",
allowableValues = "asc,desc") @QueryParam("order") @DefaultValue("desc") ReadingOrder order) { allowableValues = "asc,desc") @QueryParam("order") @DefaultValue("desc") ReadingOrder order, @ApiParam(
value = "keywords separated by spaces, 3 characters minimum",
required = true) @QueryParam("keywords") String keywords) {
Preconditions.checkNotNull(readType); Preconditions.checkNotNull(readType);
keywords = StringUtils.trimToNull(keywords);
Preconditions.checkArgument(keywords == null || StringUtils.length(keywords) >= 3);
limit = Math.min(limit, 50); limit = Math.min(limit, 50);
limit = Math.max(0, limit); limit = Math.max(0, limit);
@@ -122,7 +129,7 @@ public class CategoryREST extends AbstractREST {
if (ALL.equals(id)) { if (ALL.equals(id)) {
entries.setName("All"); entries.setName("All");
List<FeedSubscription> subscriptions = feedSubscriptionDAO.findAll(getUser()); List<FeedSubscription> subscriptions = feedSubscriptionDAO.findAll(getUser());
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(subscriptions, unreadOnly, null, newerThanDate, offset, List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(subscriptions, unreadOnly, keywords, newerThanDate, offset,
limit + 1, order, true); limit + 1, order, true);
for (FeedEntryStatus status : list) { for (FeedEntryStatus status : list) {
entries.getEntries().add( entries.getEntries().add(
@@ -143,7 +150,7 @@ public class CategoryREST extends AbstractREST {
if (parent != null) { if (parent != null) {
List<FeedCategory> categories = feedCategoryDAO.findAllChildrenCategories(getUser(), parent); List<FeedCategory> categories = feedCategoryDAO.findAllChildrenCategories(getUser(), parent);
List<FeedSubscription> subs = feedSubscriptionDAO.findByCategories(getUser(), categories); List<FeedSubscription> subs = feedSubscriptionDAO.findByCategories(getUser(), categories);
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(subs, unreadOnly, null, newerThanDate, offset, List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(subs, unreadOnly, keywords, newerThanDate, offset,
limit + 1, order, true); limit + 1, order, true);
for (FeedEntryStatus status : list) { for (FeedEntryStatus status : list) {
entries.getEntries().add( entries.getEntries().add(
@@ -162,6 +169,7 @@ public class CategoryREST extends AbstractREST {
} }
entries.setTimestamp(System.currentTimeMillis()); entries.setTimestamp(System.currentTimeMillis());
FeedUtils.removeUnwantedFromSearch(entries.getEntries(), keywords);
return Response.ok(entries).build(); return Response.ok(entries).build();
} }
@@ -180,7 +188,7 @@ public class CategoryREST extends AbstractREST {
int offset = 0; int offset = 0;
int limit = 20; int limit = 20;
Entries entries = (Entries) getCategoryEntries(id, readType, null, offset, limit, order).getEntity(); Entries entries = (Entries) getCategoryEntries(id, readType, null, offset, limit, order, null).getEntity();
SyndFeed feed = new SyndFeedImpl(); SyndFeed feed = new SyndFeedImpl();
feed.setFeedType("rss_2.0"); feed.setFeedType("rss_2.0");

View File

@@ -1,29 +1,15 @@
package com.commafeed.frontend.rest.resources; package com.commafeed.frontend.rest.resources;
import java.util.Iterator;
import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST; import javax.ws.rs.POST;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.Response.Status;
import org.apache.commons.lang.StringUtils;
import org.jsoup.Jsoup;
import com.commafeed.backend.dao.FeedEntryStatusDAO; import com.commafeed.backend.dao.FeedEntryStatusDAO;
import com.commafeed.backend.dao.FeedSubscriptionDAO; import com.commafeed.backend.dao.FeedSubscriptionDAO;
import com.commafeed.backend.model.FeedEntryStatus;
import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.UserSettings.ReadingOrder;
import com.commafeed.backend.services.ApplicationSettingsService; import com.commafeed.backend.services.ApplicationSettingsService;
import com.commafeed.backend.services.FeedEntryService; import com.commafeed.backend.services.FeedEntryService;
import com.commafeed.frontend.model.Entries;
import com.commafeed.frontend.model.Entry;
import com.commafeed.frontend.model.request.MarkRequest; import com.commafeed.frontend.model.request.MarkRequest;
import com.commafeed.frontend.model.request.MultipleMarkRequest; import com.commafeed.frontend.model.request.MultipleMarkRequest;
import com.commafeed.frontend.model.request.StarRequest; import com.commafeed.frontend.model.request.StarRequest;
@@ -87,63 +73,6 @@ public class EntryREST extends AbstractREST {
return Response.ok(Status.OK).build(); return Response.ok(Status.OK).build();
} }
@Path("/search")
@GET
@ApiOperation(
value = "Search for entries",
notes = "Look through title and content of entries by keywords",
responseClass = "com.commafeed.frontend.model.Entries")
public Response searchEntries(
@ApiParam(value = "keywords separated by spaces, 3 characters minimum", required = true) @QueryParam("keywords") String keywords,
@ApiParam(value = "offset for paging") @DefaultValue("0") @QueryParam("offset") int offset, @ApiParam(
value = "limit for paging") @DefaultValue("-1") @QueryParam("limit") int limit) {
keywords = StringUtils.trimToEmpty(keywords);
limit = Math.min(limit, 50);
Preconditions.checkArgument(StringUtils.length(keywords) >= 3);
Entries entries = new Entries();
entries.setOffset(offset);
entries.setLimit(limit);
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(getUser());
List<FeedEntryStatus> entriesStatus = feedEntryStatusDAO.findBySubscriptions(subs, false, keywords, null, offset, limit + 1,
ReadingOrder.desc, true);
for (FeedEntryStatus status : entriesStatus) {
entries.getEntries().add(
Entry.build(status, applicationSettingsService.get().getPublicUrl(), applicationSettingsService.get()
.isImageProxyEnabled()));
}
boolean hasMore = entries.getEntries().size() > limit;
if (hasMore) {
entries.setHasMore(true);
entries.getEntries().remove(entries.getEntries().size() - 1);
}
removeUnwanted(entries.getEntries(), keywords);
entries.setName("Search for : " + keywords);
entries.setTimestamp(System.currentTimeMillis());
return Response.ok(entries).build();
}
private void removeUnwanted(List<Entry> entries, String keywords) {
Iterator<Entry> it = entries.iterator();
while (it.hasNext()) {
Entry entry = it.next();
boolean keep = true;
for (String keyword : keywords.split(" ")) {
String title = Jsoup.parse(entry.getTitle()).text();
String content = Jsoup.parse(entry.getContent()).text();
if (!StringUtils.containsIgnoreCase(content, keyword) && !StringUtils.containsIgnoreCase(title, keyword)) {
keep = false;
break;
}
}
if (!keep) {
it.remove();
}
}
}
} }

View File

@@ -125,7 +125,7 @@ public class FeedREST extends AbstractREST {
@Context @Context
private HttpServletRequest request; private HttpServletRequest request;
@Inject @Inject
ApplicationSettingsService applicationSettingsService; ApplicationSettingsService applicationSettingsService;
@@ -140,10 +140,15 @@ public class FeedREST extends AbstractREST {
@ApiParam(value = "offset for paging") @DefaultValue("0") @QueryParam("offset") int offset, @ApiParam( @ApiParam(value = "offset for paging") @DefaultValue("0") @QueryParam("offset") int offset, @ApiParam(
value = "limit for paging, default 20, maximum 50") @DefaultValue("20") @QueryParam("limit") int limit, @ApiParam( value = "limit for paging, default 20, maximum 50") @DefaultValue("20") @QueryParam("limit") int limit, @ApiParam(
value = "date ordering", value = "date ordering",
allowableValues = "asc,desc") @QueryParam("order") @DefaultValue("desc") ReadingOrder order) { allowableValues = "asc,desc") @QueryParam("order") @DefaultValue("desc") ReadingOrder order, @ApiParam(
value = "keywords separated by spaces, 3 characters minimum",
required = true) @QueryParam("keywords") String keywords) {
Preconditions.checkNotNull(id); Preconditions.checkNotNull(id);
Preconditions.checkNotNull(readType); Preconditions.checkNotNull(readType);
keywords = StringUtils.trimToNull(keywords);
Preconditions.checkArgument(keywords == null || StringUtils.length(keywords) >= 3);
limit = Math.min(limit, 50); limit = Math.min(limit, 50);
limit = Math.max(0, limit); limit = Math.max(0, limit);
@@ -163,7 +168,7 @@ public class FeedREST extends AbstractREST {
entries.setErrorCount(subscription.getFeed().getErrorCount()); entries.setErrorCount(subscription.getFeed().getErrorCount());
entries.setFeedLink(subscription.getFeed().getLink()); entries.setFeedLink(subscription.getFeed().getLink());
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(Arrays.asList(subscription), unreadOnly, null, List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(Arrays.asList(subscription), unreadOnly, keywords,
newerThanDate, offset, limit + 1, order, true); newerThanDate, offset, limit + 1, order, true);
for (FeedEntryStatus status : list) { for (FeedEntryStatus status : list) {
@@ -180,6 +185,7 @@ public class FeedREST extends AbstractREST {
} }
entries.setTimestamp(System.currentTimeMillis()); entries.setTimestamp(System.currentTimeMillis());
FeedUtils.removeUnwantedFromSearch(entries.getEntries(), keywords);
return Response.ok(entries).build(); return Response.ok(entries).build();
} }
@@ -197,7 +203,7 @@ public class FeedREST extends AbstractREST {
int offset = 0; int offset = 0;
int limit = 20; int limit = 20;
Entries entries = (Entries) getFeedEntries(id, readType, null, offset, limit, order).getEntity(); Entries entries = (Entries) getFeedEntries(id, readType, null, offset, limit, order, null).getEntity();
SyndFeed feed = new SyndFeedImpl(); SyndFeed feed = new SyndFeedImpl();
feed.setFeedType("rss_2.0"); feed.setFeedType("rss_2.0");

View File

@@ -21,6 +21,10 @@ module.run(['$rootScope', function($rootScope) {
// args.all // args.all
$rootScope.$broadcast('reload', args || {}); $rootScope.$broadcast('reload', args || {});
}); });
$rootScope.$on('emitEntrySearch', function(event, args) {
// args.keywords
$rootScope.$broadcast('entrySearch', args);
});
$rootScope.$on('emitFeedSearch', function(event, args) { $rootScope.$on('emitFeedSearch', function(event, args) {
$rootScope.$broadcast('feedSearch'); $rootScope.$broadcast('feedSearch');
}); });
@@ -440,6 +444,7 @@ module.controller('ToolbarCtrl', [
return ($http.pendingRequests.length + $.active); return ($http.pendingRequests.length + $.active);
} }
$scope.keywords = $location.search().q;
$scope.session = ProfileService.get(); $scope.session = ProfileService.get();
$scope.ServerService = ServerService.get(); $scope.ServerService = ServerService.get();
$scope.settingsService = SettingsService; $scope.settingsService = SettingsService;
@@ -509,15 +514,11 @@ module.controller('ToolbarCtrl', [
markAll(new Date().getTime() - 1209600000); markAll(new Date().getTime() - 1209600000);
}; };
$scope.keywords = $stateParams._keywords;
$scope.search = function() { $scope.search = function() {
if ($scope.keywords == $stateParams._keywords) { $location.search('q', $scope.keywords);
$scope.refresh(); $scope.$emit('emitEntrySearch', {
} else { keywords : $scope.keywords
$state.transitionTo('feeds.search', { });
_keywords : $scope.keywords
});
}
}; };
$scope.showButtons = function() { $scope.showButtons = function() {
return !$stateParams._keywords; return !$stateParams._keywords;
@@ -664,19 +665,21 @@ module.controller('FeedListCtrl', [
'$route', '$route',
'$state', '$state',
'$window', '$window',
'$location',
'EntryService', 'EntryService',
'SettingsService', 'SettingsService',
'FeedService', 'FeedService',
'CategoryService', 'CategoryService',
'AnalyticsService', 'AnalyticsService',
function($scope, $stateParams, $http, $route, $state, $window, EntryService, SettingsService, FeedService, CategoryService, function($scope, $stateParams, $http, $route, $state, $window, $location, EntryService, SettingsService, FeedService, CategoryService,
AnalyticsService) { AnalyticsService) {
AnalyticsService.track(); AnalyticsService.track();
$scope.keywords = $location.search().q;
$scope.selectedType = $stateParams._type; $scope.selectedType = $stateParams._type;
$scope.selectedId = $stateParams._id; $scope.selectedId = $stateParams._id;
$scope.keywords = $stateParams._keywords;
$scope.name = null; $scope.name = null;
$scope.message = null; $scope.message = null;
@@ -738,22 +741,16 @@ module.controller('FeedListCtrl', [
$scope.hasMore = data.hasMore; $scope.hasMore = data.hasMore;
$scope.feedLink = data.feedLink; $scope.feedLink = data.feedLink;
}; };
if (!$scope.keywords) {
var service = $scope.selectedType == 'feed' ? FeedService : CategoryService; var service = $scope.selectedType == 'feed' ? FeedService : CategoryService;
service.entries({ service.entries({
id : $scope.selectedId, id : $scope.selectedId,
readType : $scope.settingsService.settings.readingMode, readType : $scope.keywords ? 'all' : $scope.settingsService.settings.readingMode,
order : $scope.settingsService.settings.readingOrder, order : $scope.settingsService.settings.readingOrder,
offset : offset, offset : offset,
limit : limit limit : limit,
}, callback); keywords : $scope.keywords
} else { }, callback);
EntryService.search({
keywords : $scope.keywords,
offset : $scope.entries.length,
limit : limit
}, callback);
}
}; };
$scope.goToFeed = function(id) { $scope.goToFeed = function(id) {
@@ -1152,7 +1149,9 @@ module.controller('FeedListCtrl', [
$scope.markAll(args.olderThan); $scope.markAll(args.olderThan);
}); });
$scope.$on('reload', function(event, args) { var reload = function(all, keywords) {
$scope.keywords = keywords;
$location.search('q', keywords);
delete $scope.current; delete $scope.current;
$scope.name = null; $scope.name = null;
$scope.entries = []; $scope.entries = [];
@@ -1163,13 +1162,21 @@ module.controller('FeedListCtrl', [
$scope.hasMore = true; $scope.hasMore = true;
$scope.loadMoreEntries(); $scope.loadMoreEntries();
if (args.all) { if (all) {
FeedService.refreshAll(); FeedService.refreshAll();
} else if ($scope.selectedType == 'feed') { } else if ($scope.selectedType == 'feed') {
FeedService.refresh({ FeedService.refresh({
id : $stateParams._id id : $stateParams._id
}); });
} }
};
$scope.$on('entrySearch', function(event, args) {
reload(null, args.keywords);
});
$scope.$on('reload', function(event, args) {
reload(args.all, null);
}); });
}]); }]);