forked from Archives/Athou_commafeed
build icon url server side
This commit is contained in:
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -76,6 +76,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
|
||||||
|
|||||||
@@ -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;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
}]
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user