build icon url server side

This commit is contained in:
Athou
2013-05-26 15:36:55 +02:00
parent c6a8c288ce
commit 5124a092d3
9 changed files with 84 additions and 34 deletions

View File

@@ -1,5 +1,7 @@
package com.commafeed.backend.feeds; package com.commafeed.backend.feeds;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
@@ -14,12 +16,16 @@ import org.jsoup.nodes.Document.OutputSettings;
import org.jsoup.nodes.Entities.EscapeMode; import org.jsoup.nodes.Entities.EscapeMode;
import org.jsoup.safety.Whitelist; import org.jsoup.safety.Whitelist;
import org.mozilla.universalchardet.UniversalDetector; import org.mozilla.universalchardet.UniversalDetector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntry;
import com.google.api.client.util.Lists; import com.google.api.client.util.Lists;
public class FeedUtils { public class FeedUtils {
protected static Logger log = LoggerFactory.getLogger(FeedUtils.class);
public static String truncate(String string, int length) { public static String truncate(String string, int length) {
if (string != null) { if (string != null) {
string = string.substring(0, Math.min(length, string.length())); string = string.substring(0, Math.min(length, string.length()));
@@ -204,4 +210,26 @@ public class FeedUtils {
return baseUrl + url; return baseUrl + url;
} }
public static String getFaviconUrl(String url, String publicUrl) {
String defaultIcon = removeTrailingSlash(publicUrl)
+ "/images/default_favicon.gif";
if (StringUtils.isBlank(url)) {
return defaultIcon;
}
int index = Math.max(url.length(), url.lastIndexOf('?'));
StringBuilder iconUrl = new StringBuilder(
"https://getfavicon.appspot.com/");
try {
iconUrl.append(URLEncoder.encode(url.substring(0, index), "UTF-8"));
} catch (UnsupportedEncodingException e) {
// never happens
log.error(e.getMessage(), e);
}
iconUrl.append("?defaulticon=none");
return iconUrl.toString();
}
} }

View File

@@ -8,6 +8,7 @@ import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlRootElement;
import com.commafeed.backend.feeds.FeedUtils;
import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedEntryStatus; import com.commafeed.backend.model.FeedEntryStatus;
import com.sun.syndication.feed.synd.SyndContentImpl; import com.sun.syndication.feed.synd.SyndContentImpl;
@@ -22,7 +23,7 @@ import com.wordnik.swagger.annotations.ApiProperty;
@ApiClass("Entry details") @ApiClass("Entry details")
public class Entry implements Serializable { public class Entry implements Serializable {
public static Entry build(FeedEntryStatus status) { public static Entry build(FeedEntryStatus status, String publicUrl) {
Entry entry = new Entry(); Entry entry = new Entry();
FeedEntry feedEntry = status.getEntry(); FeedEntry feedEntry = status.getEntry();
@@ -44,6 +45,8 @@ public class Entry implements Serializable {
entry.setFeedId(String.valueOf(status.getSubscription().getId())); entry.setFeedId(String.valueOf(status.getSubscription().getId()));
entry.setFeedUrl(status.getSubscription().getFeed().getUrl()); entry.setFeedUrl(status.getSubscription().getFeed().getUrl());
entry.setFeedLink(status.getSubscription().getFeed().getLink()); entry.setFeedLink(status.getSubscription().getFeed().getLink());
entry.setIconUrl(FeedUtils.getFaviconUrl(status.getSubscription()
.getFeed().getLink(), publicUrl));
return entry; return entry;
} }
@@ -98,6 +101,9 @@ public class Entry implements Serializable {
@ApiProperty("this entry's website url") @ApiProperty("this entry's website url")
private String feedLink; private String feedLink;
@ApiProperty(value = "The favicon url to use for this feed")
private String iconUrl;
@ApiProperty("entry url") @ApiProperty("entry url")
private String url; private String url;
@@ -227,4 +233,12 @@ public class Entry implements Serializable {
this.author = author; this.author = author;
} }
public String getIconUrl() {
return iconUrl;
}
public void setIconUrl(String iconUrl) {
this.iconUrl = iconUrl;
}
} }

View File

@@ -8,6 +8,7 @@ import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlRootElement;
import com.commafeed.backend.feeds.FeedUtils;
import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.FeedCategory; import com.commafeed.backend.model.FeedCategory;
import com.commafeed.backend.model.FeedSubscription; import com.commafeed.backend.model.FeedSubscription;
@@ -21,7 +22,7 @@ import com.wordnik.swagger.annotations.ApiProperty;
public class Subscription implements Serializable { public class Subscription implements Serializable {
public static Subscription build(FeedSubscription subscription, public static Subscription build(FeedSubscription subscription,
long unreadCount) { String publicUrl, long unreadCount) {
Date now = Calendar.getInstance().getTime(); Date now = Calendar.getInstance().getTime();
FeedCategory category = subscription.getCategory(); FeedCategory category = subscription.getCategory();
Feed feed = subscription.getFeed(); Feed feed = subscription.getFeed();
@@ -32,6 +33,7 @@ public class Subscription implements Serializable {
sub.setErrorCount(feed.getErrorCount()); sub.setErrorCount(feed.getErrorCount());
sub.setFeedUrl(feed.getUrl()); sub.setFeedUrl(feed.getUrl());
sub.setFeedLink(feed.getLink()); sub.setFeedLink(feed.getLink());
sub.setIconUrl(FeedUtils.getFaviconUrl(feed.getLink(), publicUrl));
sub.setLastRefresh(feed.getLastUpdated()); sub.setLastRefresh(feed.getLastUpdated());
sub.setNextRefresh((feed.getDisabledUntil() != null && feed sub.setNextRefresh((feed.getDisabledUntil() != null && feed
.getDisabledUntil().before(now)) ? null : feed .getDisabledUntil().before(now)) ? null : feed
@@ -66,6 +68,9 @@ public class Subscription implements Serializable {
@ApiProperty(value = "this subscription's website url", required = true) @ApiProperty(value = "this subscription's website url", required = true)
private String feedLink; private String feedLink;
@ApiProperty(value = "The favicon url to use for this feed")
private String iconUrl;
@ApiProperty(value = "unread count", required = true) @ApiProperty(value = "unread count", required = true)
private long unread; private long unread;
@@ -152,4 +157,12 @@ public class Subscription implements Serializable {
this.nextRefresh = nextRefresh; this.nextRefresh = nextRefresh;
} }
public String getIconUrl() {
return iconUrl;
}
public void setIconUrl(String iconUrl) {
this.iconUrl = iconUrl;
}
} }

View File

@@ -70,6 +70,7 @@ public class CategoryREST extends AbstractResourceREST {
@ApiParam(value = "date ordering", allowableValues = "asc,desc") @QueryParam("order") @DefaultValue("desc") ReadingOrder order) { @ApiParam(value = "date ordering", allowableValues = "asc,desc") @QueryParam("order") @DefaultValue("desc") ReadingOrder order) {
Preconditions.checkNotNull(readType); Preconditions.checkNotNull(readType);
limit = Math.min(limit, 50);
Entries entries = new Entries(); Entries entries = new Entries();
boolean unreadOnly = readType == ReadType.unread; boolean unreadOnly = readType == ReadType.unread;
@@ -82,7 +83,9 @@ public class CategoryREST extends AbstractResourceREST {
List<FeedEntryStatus> unreadEntries = feedEntryStatusDAO.findAll( List<FeedEntryStatus> unreadEntries = feedEntryStatusDAO.findAll(
getUser(), unreadOnly, offset, limit, order, true); getUser(), unreadOnly, offset, limit, order, true);
for (FeedEntryStatus status : unreadEntries) { for (FeedEntryStatus status : unreadEntries) {
entries.getEntries().add(Entry.build(status)); entries.getEntries().add(
Entry.build(status, applicationSettingsService.get()
.getPublicUrl()));
} }
} else if (STARRED.equals(id)) { } else if (STARRED.equals(id)) {
@@ -90,7 +93,9 @@ public class CategoryREST extends AbstractResourceREST {
List<FeedEntryStatus> starred = feedEntryStatusDAO.findStarred( List<FeedEntryStatus> starred = feedEntryStatusDAO.findStarred(
getUser(), offset, limit, order, true); getUser(), offset, limit, order, true);
for (FeedEntryStatus status : starred) { for (FeedEntryStatus status : starred) {
entries.getEntries().add(Entry.build(status)); entries.getEntries().add(
Entry.build(status, applicationSettingsService.get()
.getPublicUrl()));
} }
} else { } else {
FeedCategory feedCategory = feedCategoryDAO.findById(getUser(), FeedCategory feedCategory = feedCategoryDAO.findById(getUser(),
@@ -102,7 +107,9 @@ public class CategoryREST extends AbstractResourceREST {
.findByCategories(childrenCategories, getUser(), .findByCategories(childrenCategories, getUser(),
unreadOnly, offset, limit, order, true); unreadOnly, offset, limit, order, true);
for (FeedEntryStatus status : unreadEntries) { for (FeedEntryStatus status : unreadEntries) {
entries.getEntries().add(Entry.build(status)); entries.getEntries().add(
Entry.build(status, applicationSettingsService
.get().getPublicUrl()));
} }
entries.setName(feedCategory.getName()); entries.setName(feedCategory.getName());
} }
@@ -347,7 +354,9 @@ public class CategoryREST extends AbstractResourceREST {
.equals(subscription.getCategory().getId(), id))) { .equals(subscription.getCategory().getId(), id))) {
Long size = unreadCount.get(subscription.getId()); Long size = unreadCount.get(subscription.getId());
long unread = size == null ? 0 : size; long unread = size == null ? 0 : size;
Subscription sub = Subscription.build(subscription, unread); Subscription sub = Subscription
.build(subscription, applicationSettingsService.get()
.getPublicUrl(), unread);
category.getFeeds().add(sub); category.getFeeds().add(sub);
} }
} }

View File

@@ -63,6 +63,7 @@ public class EntryREST extends AbstractResourceREST {
@ApiParam(value = "offset for paging") @DefaultValue("0") @QueryParam("offset") int offset, @ApiParam(value = "offset for paging") @DefaultValue("0") @QueryParam("offset") int offset,
@ApiParam(value = "limit for paging") @DefaultValue("-1") @QueryParam("limit") int limit) { @ApiParam(value = "limit for paging") @DefaultValue("-1") @QueryParam("limit") int limit) {
keywords = StringUtils.trimToEmpty(keywords); keywords = StringUtils.trimToEmpty(keywords);
limit = Math.min(limit, 50);
Preconditions.checkArgument(StringUtils.length(keywords) >= 3); Preconditions.checkArgument(StringUtils.length(keywords) >= 3);
Entries entries = new Entries(); Entries entries = new Entries();
@@ -71,7 +72,8 @@ public class EntryREST extends AbstractResourceREST {
List<FeedEntryStatus> entriesStatus = feedEntryStatusDAO List<FeedEntryStatus> entriesStatus = feedEntryStatusDAO
.findByKeywords(getUser(), keywords, offset, limit); .findByKeywords(getUser(), keywords, offset, limit);
for (FeedEntryStatus status : entriesStatus) { for (FeedEntryStatus status : entriesStatus) {
list.add(Entry.build(status)); list.add(Entry.build(status, applicationSettingsService.get()
.getPublicUrl()));
} }
entries.setName("Search for : " + keywords); entries.setName("Search for : " + keywords);

View File

@@ -75,6 +75,8 @@ public class FeedREST extends AbstractResourceREST {
Preconditions.checkNotNull(id); Preconditions.checkNotNull(id);
Preconditions.checkNotNull(readType); Preconditions.checkNotNull(readType);
limit = Math.min(limit, 50);
Entries entries = new Entries(); Entries entries = new Entries();
boolean unreadOnly = readType == ReadType.unread; boolean unreadOnly = readType == ReadType.unread;
@@ -90,7 +92,9 @@ public class FeedREST extends AbstractResourceREST {
.findByFeed(subscription.getFeed(), getUser(), unreadOnly, .findByFeed(subscription.getFeed(), getUser(), unreadOnly,
offset, limit, order, true); offset, limit, order, true);
for (FeedEntryStatus status : unreadEntries) { for (FeedEntryStatus status : unreadEntries) {
entries.getEntries().add(Entry.build(status)); entries.getEntries().add(
Entry.build(status, applicationSettingsService.get()
.getPublicUrl()));
} }
} }
@@ -221,7 +225,9 @@ public class FeedREST extends AbstractResourceREST {
if (sub == null) { if (sub == null) {
return Response.status(Status.NOT_FOUND).build(); return Response.status(Status.NOT_FOUND).build();
} }
return Response.ok(Subscription.build(sub, 0)).build(); return Response.ok(
Subscription.build(sub, applicationSettingsService.get()
.getPublicUrl(), 0)).build();
} }
@POST @POST

View File

@@ -40,29 +40,7 @@ module.directive('favicon', function() {
url : '=' url : '='
}, },
replace : true, replace : true,
template : '<img ng-src="{{iconUrl()}}" class="favicon" onError="this.src=\'images/default_favicon.gif\'"></img>', template : '<img ng-src="{{url}}" class="favicon" onError="this.src=\'images/default_favicon.gif\'"></img>'
controller : ['$scope', function($scope) {
$scope.iconUrl = function() {
var url = $scope.url;
var current = window.location.href;
var baseUrl = current.substring(0, current.lastIndexOf('#'));
var defaultIcon = baseUrl + 'images/default_favicon.gif';
if (!url) {
return defaultIcon;
}
var index = Math.max(url.length, url.lastIndexOf('?'));
var iconUrl = '//getfavicon.appspot.com/';
iconUrl += encodeURIComponent(url.substring(0, index));
iconUrl += '?defaulticon=none';
return iconUrl;
};
}]
}; };
}); });

View File

@@ -30,7 +30,7 @@
</div> </div>
<a ng-click="feedClicked(feed.id)" class="feed-link" <a ng-click="feedClicked(feed.id)" class="feed-link"
ng-class="{error: feed.message && feed.errorCount > 10, selected: (feed.id == selectedId && selectedType == 'feed'), unread: feed.unread }"> ng-class="{error: feed.message && feed.errorCount > 10, selected: (feed.id == selectedId && selectedType == 'feed'), unread: feed.unread }">
<favicon url="feed.feedLink" /> <favicon url="feed.iconUrl" />
{{feed.name}} {{feedCountLabel(feed)}} {{feed.name}} {{feedCountLabel(feed)}}
</a> </a>
</li> </li>

View File

@@ -15,7 +15,7 @@
<i ng-class="{'icon-star icon-star-yellow': entry.starred, 'icon-star-empty': !entry.starred}" <i ng-class="{'icon-star icon-star-yellow': entry.starred, 'icon-star-empty': !entry.starred}"
class="pointer"></i> class="pointer"></i>
</span> </span>
<favicon url="entry.feedLink" /> <favicon url="entry.iconUrl" />
{{entry.feedName}} {{entry.feedName}}
</span> </span>
<span class="entry-date visible-desktop">{{entry.date | entryDate}}</span> <span class="entry-date visible-desktop">{{entry.date | entryDate}}</span>