forked from Archives/Athou_commafeed
Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f4d62ab47 | ||
|
|
a7b826bd4f | ||
|
|
407481faa6 | ||
|
|
305b68546c | ||
|
|
136c41c6aa | ||
|
|
587b25b18b | ||
|
|
beaa40ad65 | ||
|
|
1389a5a238 | ||
|
|
2f34ff8a9f | ||
|
|
d3626b0e7c | ||
|
|
bb4529b6f1 | ||
|
|
dd94125d52 | ||
|
|
a7149e3740 | ||
|
|
b64d041385 | ||
|
|
cc04bdfbc5 | ||
|
|
d8c772ed5e | ||
|
|
dfcc4eeebd | ||
|
|
e491841d4a | ||
|
|
ccb72837b3 | ||
|
|
6560fc9d05 | ||
|
|
14d5879735 | ||
|
|
7fa8bef3de | ||
|
|
966caae727 | ||
|
|
a14484ee03 | ||
|
|
fb9b42ab12 | ||
|
|
6974abdb95 | ||
|
|
65efdeb1df | ||
|
|
54a39ea0a9 | ||
|
|
641350cbde | ||
|
|
06ece8f5ee | ||
|
|
ca87f1c47a | ||
|
|
c38ddb5d00 | ||
|
|
1acd7c4a01 | ||
|
|
d92c2ebdf7 | ||
|
|
8f19e9408e | ||
|
|
3ecb47da5a | ||
|
|
ae03b42c6d | ||
|
|
ee4eb9bb07 | ||
|
|
a0be2e0879 | ||
|
|
a3414d7156 |
@@ -75,11 +75,14 @@ It will generate a zip file at `target/commafeed.zip` with everything you need t
|
|||||||
* If you'd like to change the default port (8082), edit `conf/server.xml` and look for `<Connector port="8082" protocol="HTTP/1.1"`. Change the port to the value you'd like to use.
|
* If you'd like to change the default port (8082), edit `conf/server.xml` and look for `<Connector port="8082" protocol="HTTP/1.1"`. Change the port to the value you'd like to use.
|
||||||
* CommaFeed will run on the `/commafeed` context. If you'd like to change the context, go to `webapps` and rename `commafeed.war`. Use the special name `ROOT.war` to deploy to the root context.
|
* CommaFeed will run on the `/commafeed` context. If you'd like to change the context, go to `webapps` and rename `commafeed.war`. Use the special name `ROOT.war` to deploy to the root context.
|
||||||
* To start and stop the application, use `bin/startup.sh` and `bin/shutdown.sh` on Linux (you need to `chmod +x bin/*.sh`) or `bin\startup.bat` and `bin\shutdown.bat` on Windows.
|
* To start and stop the application, use `bin/startup.sh` and `bin/shutdown.sh` on Linux (you need to `chmod +x bin/*.sh`) or `bin\startup.bat` and `bin\shutdown.bat` on Windows.
|
||||||
|
If you use the embedded database, note that the database file will be created in the current directory, so make sure you always start the app in the same directory. You can optionally set an absolute path instead of a relative one in `tomee.xml`.
|
||||||
* To update the application with a newer version, pull the latest changes and use the same command you used to build the complete TomEE package, but without the `tomee:build` part (keep `-Pprod -P<database>`).
|
* To update the application with a newer version, pull the latest changes and use the same command you used to build the complete TomEE package, but without the `tomee:build` part (keep `-Pprod -P<database>`).
|
||||||
This will generate the file `target/commafeed.war`. Copy this file to your tomee `webapps/` directory.
|
This will generate the file `target/commafeed.war`. Copy this file to your tomee `webapps/` directory.
|
||||||
* The application is online at [http://localhost:8082/commafeed](http://localhost:8082/commafeed). Don't forget to set the public URL in the admin settings.
|
* The application is online at [http://localhost:8082/commafeed](http://localhost:8082/commafeed). Don't forget to set the public URL in the admin settings.
|
||||||
* The default user is `admin` and the password is `admin`.
|
* The default user is `admin` and the password is `admin`.
|
||||||
|
|
||||||
|
You can use nginix or apache as a proxy http server. Note that when using apache, the `ProxyPreserveHost on` option should be `set in your config file.
|
||||||
|
|
||||||
Local development
|
Local development
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
|||||||
21
pom.xml
21
pom.xml
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<groupId>com.commafeed</groupId>
|
<groupId>com.commafeed</groupId>
|
||||||
<artifactId>commafeed</artifactId>
|
<artifactId>commafeed</artifactId>
|
||||||
<version>1.2.0</version>
|
<version>1.3.0</version>
|
||||||
<packaging>war</packaging>
|
<packaging>war</packaging>
|
||||||
<name>CommaFeed</name>
|
<name>CommaFeed</name>
|
||||||
|
|
||||||
@@ -336,23 +336,23 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.wicket</groupId>
|
<groupId>org.apache.wicket</groupId>
|
||||||
<artifactId>wicket-core</artifactId>
|
<artifactId>wicket-core</artifactId>
|
||||||
<version>6.9.1</version>
|
<version>6.10.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.wicket</groupId>
|
<groupId>org.apache.wicket</groupId>
|
||||||
<artifactId>wicket-auth-roles</artifactId>
|
<artifactId>wicket-auth-roles</artifactId>
|
||||||
<version>6.9.1</version>
|
<version>6.10.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.wicket</groupId>
|
<groupId>org.apache.wicket</groupId>
|
||||||
<artifactId>wicket-extensions</artifactId>
|
<artifactId>wicket-extensions</artifactId>
|
||||||
<version>6.9.1</version>
|
<version>6.10.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.wicket</groupId>
|
<groupId>org.apache.wicket</groupId>
|
||||||
<artifactId>wicket-cdi</artifactId>
|
<artifactId>wicket-cdi</artifactId>
|
||||||
<version>6.9.1</version>
|
<version>6.10.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ro.isdc.wro4j</groupId>
|
<groupId>ro.isdc.wro4j</groupId>
|
||||||
@@ -372,6 +372,17 @@
|
|||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.codahale.metrics</groupId>
|
||||||
|
<artifactId>metrics-core</artifactId>
|
||||||
|
<version>3.0.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.codahale.metrics</groupId>
|
||||||
|
<artifactId>metrics-json</artifactId>
|
||||||
|
<version>3.0.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
|
|||||||
@@ -19,12 +19,14 @@ import org.apache.commons.lang.StringUtils;
|
|||||||
import org.apache.http.Header;
|
import org.apache.http.Header;
|
||||||
import org.apache.http.HttpEntity;
|
import org.apache.http.HttpEntity;
|
||||||
import org.apache.http.HttpHeaders;
|
import org.apache.http.HttpHeaders;
|
||||||
|
import org.apache.http.HttpHost;
|
||||||
import org.apache.http.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
import org.apache.http.HttpStatus;
|
import org.apache.http.HttpStatus;
|
||||||
import org.apache.http.client.ClientProtocolException;
|
import org.apache.http.client.ClientProtocolException;
|
||||||
import org.apache.http.client.HttpClient;
|
import org.apache.http.client.HttpClient;
|
||||||
import org.apache.http.client.HttpResponseException;
|
import org.apache.http.client.HttpResponseException;
|
||||||
import org.apache.http.client.methods.HttpGet;
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.client.methods.HttpUriRequest;
|
||||||
import org.apache.http.client.params.CookiePolicy;
|
import org.apache.http.client.params.CookiePolicy;
|
||||||
import org.apache.http.client.params.HttpClientParams;
|
import org.apache.http.client.params.HttpClientParams;
|
||||||
import org.apache.http.conn.ClientConnectionManager;
|
import org.apache.http.conn.ClientConnectionManager;
|
||||||
@@ -39,6 +41,9 @@ import org.apache.http.impl.client.SystemDefaultHttpClient;
|
|||||||
import org.apache.http.params.HttpConnectionParams;
|
import org.apache.http.params.HttpConnectionParams;
|
||||||
import org.apache.http.params.HttpParams;
|
import org.apache.http.params.HttpParams;
|
||||||
import org.apache.http.params.HttpProtocolParams;
|
import org.apache.http.params.HttpProtocolParams;
|
||||||
|
import org.apache.http.protocol.BasicHttpContext;
|
||||||
|
import org.apache.http.protocol.ExecutionContext;
|
||||||
|
import org.apache.http.protocol.HttpContext;
|
||||||
import org.apache.http.util.EntityUtils;
|
import org.apache.http.util.EntityUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -93,6 +98,8 @@ public class HttpGetter {
|
|||||||
HttpClient client = newClient(timeout);
|
HttpClient client = newClient(timeout);
|
||||||
try {
|
try {
|
||||||
HttpGet httpget = new HttpGet(url);
|
HttpGet httpget = new HttpGet(url);
|
||||||
|
HttpContext context = new BasicHttpContext();
|
||||||
|
|
||||||
httpget.addHeader(HttpHeaders.ACCEPT_LANGUAGE, ACCEPT_LANGUAGE);
|
httpget.addHeader(HttpHeaders.ACCEPT_LANGUAGE, ACCEPT_LANGUAGE);
|
||||||
httpget.addHeader(HttpHeaders.PRAGMA, PRAGMA_NO_CACHE);
|
httpget.addHeader(HttpHeaders.PRAGMA, PRAGMA_NO_CACHE);
|
||||||
httpget.addHeader(HttpHeaders.CACHE_CONTROL, CACHE_CONTROL_NO_CACHE);
|
httpget.addHeader(HttpHeaders.CACHE_CONTROL, CACHE_CONTROL_NO_CACHE);
|
||||||
@@ -107,7 +114,7 @@ public class HttpGetter {
|
|||||||
|
|
||||||
HttpResponse response = null;
|
HttpResponse response = null;
|
||||||
try {
|
try {
|
||||||
response = client.execute(httpget);
|
response = client.execute(httpget, context);
|
||||||
int code = response.getStatusLine().getStatusCode();
|
int code = response.getStatusLine().getStatusCode();
|
||||||
if (code == HttpStatus.SC_NOT_MODIFIED) {
|
if (code == HttpStatus.SC_NOT_MODIFIED) {
|
||||||
throw new NotModifiedException("'304 - not modified' http code received");
|
throw new NotModifiedException("'304 - not modified' http code received");
|
||||||
@@ -123,15 +130,14 @@ public class HttpGetter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Header lastModifiedHeader = response.getFirstHeader(HttpHeaders.LAST_MODIFIED);
|
Header lastModifiedHeader = response.getFirstHeader(HttpHeaders.LAST_MODIFIED);
|
||||||
Header eTagHeader = response.getFirstHeader(HttpHeaders.ETAG);
|
String lastModifiedHeaderValue = lastModifiedHeader == null ? null : StringUtils.trimToNull(lastModifiedHeader.getValue());
|
||||||
|
if (lastModifiedHeaderValue != null && StringUtils.equals(lastModified, lastModifiedHeaderValue)) {
|
||||||
String lastModifiedResponse = lastModifiedHeader == null ? null : StringUtils.trimToNull(lastModifiedHeader.getValue());
|
|
||||||
if (lastModified != null && StringUtils.equals(lastModified, lastModifiedResponse)) {
|
|
||||||
throw new NotModifiedException("lastModifiedHeader is the same");
|
throw new NotModifiedException("lastModifiedHeader is the same");
|
||||||
}
|
}
|
||||||
|
|
||||||
String eTagResponse = eTagHeader == null ? null : StringUtils.trimToNull(eTagHeader.getValue());
|
Header eTagHeader = response.getFirstHeader(HttpHeaders.ETAG);
|
||||||
if (eTag != null && StringUtils.equals(eTag, eTagResponse)) {
|
String eTagHeaderValue = eTagHeader == null ? null : StringUtils.trimToNull(eTagHeader.getValue());
|
||||||
|
if (eTag != null && StringUtils.equals(eTag, eTagHeaderValue)) {
|
||||||
throw new NotModifiedException("eTagHeader is the same");
|
throw new NotModifiedException("eTagHeader is the same");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,10 +150,12 @@ public class HttpGetter {
|
|||||||
contentType = entity.getContentType().getValue();
|
contentType = entity.getContentType().getValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
HttpUriRequest req = (HttpUriRequest) context.getAttribute(ExecutionContext.HTTP_REQUEST);
|
||||||
|
HttpHost host = (HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
|
||||||
|
String urlAfterRedirect = req.getURI().isAbsolute() ? req.getURI().toString() : host.toURI() + req.getURI();
|
||||||
|
|
||||||
long duration = System.currentTimeMillis() - start;
|
long duration = System.currentTimeMillis() - start;
|
||||||
result = new HttpResult(content, contentType, lastModifiedHeader == null ? null : lastModifiedHeader.getValue(),
|
result = new HttpResult(content, contentType, lastModifiedHeaderValue, eTagHeaderValue, duration, urlAfterRedirect);
|
||||||
eTagHeader == null ? null : eTagHeader.getValue(), duration);
|
|
||||||
} finally {
|
} finally {
|
||||||
client.getConnectionManager().shutdown();
|
client.getConnectionManager().shutdown();
|
||||||
}
|
}
|
||||||
@@ -161,13 +169,15 @@ public class HttpGetter {
|
|||||||
private String lastModifiedSince;
|
private String lastModifiedSince;
|
||||||
private String eTag;
|
private String eTag;
|
||||||
private long duration;
|
private long duration;
|
||||||
|
private String urlAfterRedirect;
|
||||||
|
|
||||||
public HttpResult(byte[] content, String contentType, String lastModifiedSince, String eTag, long duration) {
|
public HttpResult(byte[] content, String contentType, String lastModifiedSince, String eTag, long duration, String urlAfterRedirect) {
|
||||||
this.content = content;
|
this.content = content;
|
||||||
this.contentType = contentType;
|
this.contentType = contentType;
|
||||||
this.lastModifiedSince = lastModifiedSince;
|
this.lastModifiedSince = lastModifiedSince;
|
||||||
this.eTag = eTag;
|
this.eTag = eTag;
|
||||||
this.duration = duration;
|
this.duration = duration;
|
||||||
|
this.urlAfterRedirect = urlAfterRedirect;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getContent() {
|
public byte[] getContent() {
|
||||||
@@ -190,6 +200,9 @@ public class HttpGetter {
|
|||||||
return duration;
|
return duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getUrlAfterRedirect() {
|
||||||
|
return urlAfterRedirect;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static HttpClient newClient(int timeout) {
|
public static HttpClient newClient(int timeout) {
|
||||||
|
|||||||
@@ -1,179 +0,0 @@
|
|||||||
package com.commafeed.backend;
|
|
||||||
|
|
||||||
import javax.ejb.Singleton;
|
|
||||||
import javax.interceptor.AroundInvoke;
|
|
||||||
import javax.interceptor.InvocationContext;
|
|
||||||
import javax.persistence.EntityManager;
|
|
||||||
import javax.persistence.PersistenceContext;
|
|
||||||
|
|
||||||
import org.hibernate.Session;
|
|
||||||
import org.hibernate.SessionFactory;
|
|
||||||
import org.hibernate.stat.Statistics;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class MetricsBean {
|
|
||||||
|
|
||||||
@PersistenceContext
|
|
||||||
EntityManager em;
|
|
||||||
|
|
||||||
private Metric lastMinute = new Metric();
|
|
||||||
private Metric thisMinute = new Metric();
|
|
||||||
|
|
||||||
private Metric lastHour = new Metric();
|
|
||||||
private Metric thisHour = new Metric();
|
|
||||||
|
|
||||||
private long minuteTimestamp;
|
|
||||||
private long hourTimestamp;
|
|
||||||
|
|
||||||
@AroundInvoke
|
|
||||||
private Object roll(InvocationContext context) throws Exception {
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
if (now - minuteTimestamp > 60000) {
|
|
||||||
lastMinute = thisMinute;
|
|
||||||
thisMinute = new Metric();
|
|
||||||
minuteTimestamp = now;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (now - hourTimestamp > 60000 * 60) {
|
|
||||||
lastHour = thisHour;
|
|
||||||
thisHour = new Metric();
|
|
||||||
hourTimestamp = now;
|
|
||||||
|
|
||||||
}
|
|
||||||
return context.proceed();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void feedRefreshed() {
|
|
||||||
thisMinute.feedsRefreshed++;
|
|
||||||
thisHour.feedsRefreshed++;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void feedUpdated() {
|
|
||||||
thisHour.feedsUpdated++;
|
|
||||||
thisMinute.feedsUpdated++;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void entryInserted() {
|
|
||||||
|
|
||||||
thisHour.entriesInserted++;
|
|
||||||
thisMinute.entriesInserted++;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void entryCacheHit() {
|
|
||||||
thisHour.entryCacheHit++;
|
|
||||||
thisMinute.entryCacheHit++;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void entryCacheMiss() {
|
|
||||||
thisHour.entryCacheMiss++;
|
|
||||||
thisMinute.entryCacheMiss++;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void pushReceived(int feedCount) {
|
|
||||||
|
|
||||||
thisHour.pushNotificationsReceived++;
|
|
||||||
thisMinute.pushNotificationsReceived++;
|
|
||||||
|
|
||||||
thisHour.pushFeedsQueued += feedCount;
|
|
||||||
thisMinute.pushFeedsQueued += feedCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void threadWaited() {
|
|
||||||
thisHour.threadWaited++;
|
|
||||||
thisMinute.threadWaited++;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Metric getLastMinute() {
|
|
||||||
return lastMinute;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Metric getLastHour() {
|
|
||||||
return lastHour;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCacheStats() {
|
|
||||||
Session session = em.unwrap(Session.class);
|
|
||||||
SessionFactory sessionFactory = session.getSessionFactory();
|
|
||||||
Statistics statistics = sessionFactory.getStatistics();
|
|
||||||
return statistics.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Metric {
|
|
||||||
private int feedsRefreshed;
|
|
||||||
private int feedsUpdated;
|
|
||||||
private int entriesInserted;
|
|
||||||
private int threadWaited;
|
|
||||||
private int pushNotificationsReceived;
|
|
||||||
private int pushFeedsQueued;
|
|
||||||
private int entryCacheHit;
|
|
||||||
private int entryCacheMiss;
|
|
||||||
|
|
||||||
public int getFeedsRefreshed() {
|
|
||||||
return feedsRefreshed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFeedsRefreshed(int feedsRefreshed) {
|
|
||||||
this.feedsRefreshed = feedsRefreshed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getFeedsUpdated() {
|
|
||||||
return feedsUpdated;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFeedsUpdated(int feedsUpdated) {
|
|
||||||
this.feedsUpdated = feedsUpdated;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getEntriesInserted() {
|
|
||||||
return entriesInserted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEntriesInserted(int entriesInserted) {
|
|
||||||
this.entriesInserted = entriesInserted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getThreadWaited() {
|
|
||||||
return threadWaited;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setThreadWaited(int threadWaited) {
|
|
||||||
this.threadWaited = threadWaited;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPushNotificationsReceived() {
|
|
||||||
return pushNotificationsReceived;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPushNotificationsReceived(int pushNotificationsReceived) {
|
|
||||||
this.pushNotificationsReceived = pushNotificationsReceived;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPushFeedsQueued() {
|
|
||||||
return pushFeedsQueued;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPushFeedsQueued(int pushFeedsQueued) {
|
|
||||||
this.pushFeedsQueued = pushFeedsQueued;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getEntryCacheHit() {
|
|
||||||
return entryCacheHit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEntryCacheHit(int entryCacheHit) {
|
|
||||||
this.entryCacheHit = entryCacheHit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getEntryCacheMiss() {
|
|
||||||
return entryCacheMiss;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEntryCacheMiss(int entryCacheMiss) {
|
|
||||||
this.entryCacheMiss = entryCacheMiss;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,27 +4,26 @@ import java.util.Date;
|
|||||||
|
|
||||||
import javax.ejb.Schedule;
|
import javax.ejb.Schedule;
|
||||||
import javax.ejb.Stateless;
|
import javax.ejb.Stateless;
|
||||||
|
import javax.ejb.TransactionManagement;
|
||||||
|
import javax.ejb.TransactionManagementType;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.persistence.EntityManager;
|
|
||||||
import javax.persistence.PersistenceContext;
|
|
||||||
|
|
||||||
import com.commafeed.backend.services.ApplicationSettingsService;
|
import com.commafeed.backend.services.ApplicationSettingsService;
|
||||||
|
import com.commafeed.backend.services.DatabaseCleaningService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains all scheduled tasks
|
* Contains all scheduled tasks
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@Stateless
|
@Stateless
|
||||||
|
@TransactionManagement(TransactionManagementType.BEAN)
|
||||||
public class ScheduledTasks {
|
public class ScheduledTasks {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ApplicationSettingsService applicationSettingsService;
|
ApplicationSettingsService applicationSettingsService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
DatabaseCleaner cleaner;
|
DatabaseCleaningService cleaner;
|
||||||
|
|
||||||
@PersistenceContext
|
|
||||||
EntityManager em;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* clean old read statuses, runs every day at midnight
|
* clean old read statuses, runs every day at midnight
|
||||||
|
|||||||
@@ -290,10 +290,17 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
|||||||
setTimeout(query, applicationSettingsService.get().getQueryTimeout());
|
setTimeout(query, applicationSettingsService.get().getQueryTimeout());
|
||||||
}
|
}
|
||||||
|
|
||||||
public int deleteOldStatuses(Date olderThan) {
|
public List<FeedEntryStatus> getOldStatuses(Date olderThan, int limit) {
|
||||||
Query query = em.createNamedQuery("Statuses.deleteOld");
|
CriteriaQuery<FeedEntryStatus> query = builder.createQuery(getType());
|
||||||
query.setParameter("date", olderThan);
|
Root<FeedEntryStatus> root = query.from(getType());
|
||||||
return query.executeUpdate();
|
|
||||||
|
Predicate p1 = builder.lessThan(root.get(FeedEntryStatus_.entryInserted), olderThan);
|
||||||
|
Predicate p2 = builder.isFalse(root.get(FeedEntryStatus_.starred));
|
||||||
|
|
||||||
|
query.where(p1, p2);
|
||||||
|
TypedQuery<FeedEntryStatus> q = em.createQuery(query);
|
||||||
|
q.setMaxResults(limit);
|
||||||
|
return q.getResultList();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ public class FeedFetcher {
|
|||||||
feed.setEtagHeader(FeedUtils.truncate(result.geteTag(), 255));
|
feed.setEtagHeader(FeedUtils.truncate(result.geteTag(), 255));
|
||||||
feed.setLastContentHash(hash);
|
feed.setLastContentHash(hash);
|
||||||
fetchedFeed.setFetchDuration(result.getDuration());
|
fetchedFeed.setFetchDuration(result.getDuration());
|
||||||
|
fetchedFeed.setUrlAfterRedirect(result.getUrlAfterRedirect());
|
||||||
return fetchedFeed;
|
return fetchedFeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import java.util.concurrent.TimeUnit;
|
|||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import com.codahale.metrics.Gauge;
|
||||||
|
import com.codahale.metrics.MetricRegistry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps a {@link ThreadPoolExecutor} instance. Blocks when queue is full instead of rejecting the task. Allow priority queueing by using
|
* Wraps a {@link ThreadPoolExecutor} instance. Blocks when queue is full instead of rejecting the task. Allow priority queueing by using
|
||||||
* {@link Task} instead of {@link Runnable}
|
* {@link Task} instead of {@link Runnable}
|
||||||
@@ -19,7 +22,7 @@ public class FeedRefreshExecutor {
|
|||||||
private ThreadPoolExecutor pool;
|
private ThreadPoolExecutor pool;
|
||||||
private LinkedBlockingDeque<Runnable> queue;
|
private LinkedBlockingDeque<Runnable> queue;
|
||||||
|
|
||||||
public FeedRefreshExecutor(final String poolName, int threads, int queueCapacity) {
|
public FeedRefreshExecutor(final String poolName, int threads, int queueCapacity, MetricRegistry metrics) {
|
||||||
log.info("Creating pool {} with {} threads", poolName, threads);
|
log.info("Creating pool {} with {} threads", poolName, threads);
|
||||||
this.poolName = poolName;
|
this.poolName = poolName;
|
||||||
pool = new ThreadPoolExecutor(threads, threads, 0, TimeUnit.MILLISECONDS, queue = new LinkedBlockingDeque<Runnable>(queueCapacity) {
|
pool = new ThreadPoolExecutor(threads, threads, 0, TimeUnit.MILLISECONDS, queue = new LinkedBlockingDeque<Runnable>(queueCapacity) {
|
||||||
@@ -51,20 +54,26 @@ public class FeedRefreshExecutor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
metrics.register(MetricRegistry.name(getClass(), poolName, "active"), new Gauge<Integer>() {
|
||||||
|
@Override
|
||||||
|
public Integer getValue() {
|
||||||
|
return pool.getActiveCount();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
metrics.register(MetricRegistry.name(getClass(), poolName, "pending"), new Gauge<Integer>() {
|
||||||
|
@Override
|
||||||
|
public Integer getValue() {
|
||||||
|
return queue.size();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void execute(Task task) {
|
public void execute(Task task) {
|
||||||
pool.execute(task);
|
pool.execute(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getQueueSize() {
|
|
||||||
return queue.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getActiveCount() {
|
|
||||||
return pool.getActiveCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static interface Task extends Runnable {
|
public static interface Task extends Runnable {
|
||||||
boolean isUrgent();
|
boolean isUrgent();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.apache.commons.codec.digest.DigestUtils;
|
import org.apache.commons.codec.digest.DigestUtils;
|
||||||
import org.apache.commons.lang.time.DateUtils;
|
import org.apache.commons.lang.time.DateUtils;
|
||||||
|
|
||||||
import com.commafeed.backend.MetricsBean;
|
import com.codahale.metrics.Meter;
|
||||||
|
import com.codahale.metrics.MetricRegistry;
|
||||||
import com.commafeed.backend.dao.FeedDAO;
|
import com.commafeed.backend.dao.FeedDAO;
|
||||||
import com.commafeed.backend.model.Feed;
|
import com.commafeed.backend.model.Feed;
|
||||||
import com.commafeed.backend.services.ApplicationSettingsService;
|
import com.commafeed.backend.services.ApplicationSettingsService;
|
||||||
@@ -41,7 +42,7 @@ public class FeedRefreshTaskGiver {
|
|||||||
ApplicationSettingsService applicationSettingsService;
|
ApplicationSettingsService applicationSettingsService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
MetricsBean metricsBean;
|
MetricRegistry metrics;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FeedRefreshWorker worker;
|
FeedRefreshWorker worker;
|
||||||
@@ -54,10 +55,17 @@ public class FeedRefreshTaskGiver {
|
|||||||
|
|
||||||
private ExecutorService executor;
|
private ExecutorService executor;
|
||||||
|
|
||||||
|
private Meter feedRefreshed;
|
||||||
|
private Meter threadWaited;
|
||||||
|
private Meter refill;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
backgroundThreads = applicationSettingsService.get().getBackgroundThreads();
|
backgroundThreads = applicationSettingsService.get().getBackgroundThreads();
|
||||||
executor = Executors.newFixedThreadPool(1);
|
executor = Executors.newFixedThreadPool(1);
|
||||||
|
feedRefreshed = metrics.meter(MetricRegistry.name(getClass(), "feedRefreshed"));
|
||||||
|
threadWaited = metrics.meter(MetricRegistry.name(getClass(), "threadWaited"));
|
||||||
|
refill = metrics.meter(MetricRegistry.name(getClass(), "refill"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreDestroy
|
@PreDestroy
|
||||||
@@ -88,11 +96,11 @@ public class FeedRefreshTaskGiver {
|
|||||||
try {
|
try {
|
||||||
FeedRefreshContext context = take();
|
FeedRefreshContext context = take();
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
metricsBean.feedRefreshed();
|
feedRefreshed.mark();
|
||||||
worker.updateFeed(context);
|
worker.updateFeed(context);
|
||||||
} else {
|
} else {
|
||||||
log.debug("nothing to do, sleeping for 15s");
|
log.debug("nothing to do, sleeping for 15s");
|
||||||
metricsBean.threadWaited();
|
threadWaited.mark();
|
||||||
try {
|
try {
|
||||||
Thread.sleep(15000);
|
Thread.sleep(15000);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
@@ -138,6 +146,7 @@ public class FeedRefreshTaskGiver {
|
|||||||
* refills the refresh queue and empties the giveBack queue while at it
|
* refills the refresh queue and empties the giveBack queue while at it
|
||||||
*/
|
*/
|
||||||
private void refill() {
|
private void refill() {
|
||||||
|
refill.mark();
|
||||||
int count = Math.min(100, 3 * backgroundThreads);
|
int count = Math.min(100, 3 * backgroundThreads);
|
||||||
|
|
||||||
// first, get feeds that are up to refresh from the database
|
// first, get feeds that are up to refresh from the database
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ import org.apache.commons.collections.CollectionUtils;
|
|||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.commons.lang3.time.DateUtils;
|
import org.apache.commons.lang3.time.DateUtils;
|
||||||
|
|
||||||
import com.commafeed.backend.MetricsBean;
|
import com.codahale.metrics.Meter;
|
||||||
|
import com.codahale.metrics.MetricRegistry;
|
||||||
import com.commafeed.backend.cache.CacheService;
|
import com.commafeed.backend.cache.CacheService;
|
||||||
import com.commafeed.backend.dao.FeedDAO;
|
import com.commafeed.backend.dao.FeedDAO;
|
||||||
import com.commafeed.backend.dao.FeedEntryDAO;
|
import com.commafeed.backend.dao.FeedEntryDAO;
|
||||||
@@ -57,7 +58,7 @@ public class FeedRefreshUpdater {
|
|||||||
ApplicationSettingsService applicationSettingsService;
|
ApplicationSettingsService applicationSettingsService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
MetricsBean metricsBean;
|
MetricRegistry metrics;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FeedSubscriptionDAO feedSubscriptionDAO;
|
FeedSubscriptionDAO feedSubscriptionDAO;
|
||||||
@@ -71,12 +72,22 @@ public class FeedRefreshUpdater {
|
|||||||
private FeedRefreshExecutor pool;
|
private FeedRefreshExecutor pool;
|
||||||
private Striped<Lock> locks;
|
private Striped<Lock> locks;
|
||||||
|
|
||||||
|
private Meter entryCacheMiss;
|
||||||
|
private Meter entryCacheHit;
|
||||||
|
private Meter feedUpdated;
|
||||||
|
private Meter entryInserted;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
ApplicationSettings settings = applicationSettingsService.get();
|
ApplicationSettings settings = applicationSettingsService.get();
|
||||||
int threads = Math.max(settings.getDatabaseUpdateThreads(), 1);
|
int threads = Math.max(settings.getDatabaseUpdateThreads(), 1);
|
||||||
pool = new FeedRefreshExecutor("feed-refresh-updater", threads, Math.min(50 * threads, 1000));
|
pool = new FeedRefreshExecutor("feed-refresh-updater", threads, Math.min(50 * threads, 1000), metrics);
|
||||||
locks = Striped.lazyWeakLock(threads * 100000);
|
locks = Striped.lazyWeakLock(threads * 100000);
|
||||||
|
|
||||||
|
entryCacheMiss = metrics.meter(MetricRegistry.name(getClass(), "entryCacheMiss"));
|
||||||
|
entryCacheHit = metrics.meter(MetricRegistry.name(getClass(), "entryCacheHit"));
|
||||||
|
feedUpdated = metrics.meter(MetricRegistry.name(getClass(), "feedUpdated"));
|
||||||
|
entryInserted = metrics.meter(MetricRegistry.name(getClass(), "entryInserted"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreDestroy
|
@PreDestroy
|
||||||
@@ -116,10 +127,10 @@ public class FeedRefreshUpdater {
|
|||||||
subscriptions = feedSubscriptionDAO.findByFeed(feed);
|
subscriptions = feedSubscriptionDAO.findByFeed(feed);
|
||||||
}
|
}
|
||||||
ok &= addEntry(feed, entry, subscriptions);
|
ok &= addEntry(feed, entry, subscriptions);
|
||||||
metricsBean.entryCacheMiss();
|
entryCacheMiss.mark();
|
||||||
} else {
|
} else {
|
||||||
log.debug("cache hit for {}", entry.getUrl());
|
log.debug("cache hit for {}", entry.getUrl());
|
||||||
metricsBean.entryCacheHit();
|
entryCacheHit.mark();
|
||||||
}
|
}
|
||||||
|
|
||||||
currentEntries.add(cacheKey);
|
currentEntries.add(cacheKey);
|
||||||
@@ -147,7 +158,7 @@ public class FeedRefreshUpdater {
|
|||||||
// requeue asap
|
// requeue asap
|
||||||
feed.setDisabledUntil(new Date(0));
|
feed.setDisabledUntil(new Date(0));
|
||||||
}
|
}
|
||||||
metricsBean.feedUpdated();
|
feedUpdated.mark();
|
||||||
taskGiver.giveBack(feed);
|
taskGiver.giveBack(feed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +191,7 @@ public class FeedRefreshUpdater {
|
|||||||
if (locked1 && locked2) {
|
if (locked1 && locked2) {
|
||||||
boolean inserted = feedUpdateService.addEntry(feed, entry);
|
boolean inserted = feedUpdateService.addEntry(feed, entry);
|
||||||
if (inserted) {
|
if (inserted) {
|
||||||
metricsBean.entryInserted();
|
entryInserted.mark();
|
||||||
}
|
}
|
||||||
success = true;
|
success = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -213,13 +224,4 @@ public class FeedRefreshUpdater {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getQueueSize() {
|
|
||||||
return pool.getQueueSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getActiveCount() {
|
|
||||||
return pool.getActiveCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
|
|
||||||
import org.apache.commons.codec.digest.DigestUtils;
|
import org.apache.commons.codec.digest.DigestUtils;
|
||||||
import org.apache.commons.lang.time.DateUtils;
|
import org.apache.commons.lang.time.DateUtils;
|
||||||
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import com.codahale.metrics.MetricRegistry;
|
||||||
import com.commafeed.backend.HttpGetter.NotModifiedException;
|
import com.commafeed.backend.HttpGetter.NotModifiedException;
|
||||||
import com.commafeed.backend.feeds.FeedRefreshExecutor.Task;
|
import com.commafeed.backend.feeds.FeedRefreshExecutor.Task;
|
||||||
import com.commafeed.backend.model.ApplicationSettings;
|
import com.commafeed.backend.model.ApplicationSettings;
|
||||||
@@ -37,6 +40,9 @@ public class FeedRefreshWorker {
|
|||||||
@Inject
|
@Inject
|
||||||
FeedRefreshTaskGiver taskGiver;
|
FeedRefreshTaskGiver taskGiver;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
MetricRegistry metrics;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ApplicationSettingsService applicationSettingsService;
|
ApplicationSettingsService applicationSettingsService;
|
||||||
|
|
||||||
@@ -46,7 +52,7 @@ public class FeedRefreshWorker {
|
|||||||
private void init() {
|
private void init() {
|
||||||
ApplicationSettings settings = applicationSettingsService.get();
|
ApplicationSettings settings = applicationSettingsService.get();
|
||||||
int threads = settings.getBackgroundThreads();
|
int threads = settings.getBackgroundThreads();
|
||||||
pool = new FeedRefreshExecutor("feed-refresh-worker", threads, Math.min(20 * threads, 1000));
|
pool = new FeedRefreshExecutor("feed-refresh-worker", threads, Math.min(20 * threads, 1000), metrics);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreDestroy
|
@PreDestroy
|
||||||
@@ -58,14 +64,6 @@ public class FeedRefreshWorker {
|
|||||||
pool.execute(new FeedTask(context));
|
pool.execute(new FeedTask(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getQueueSize() {
|
|
||||||
return pool.getQueueSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getActiveCount() {
|
|
||||||
return pool.getActiveCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class FeedTask implements Task {
|
private class FeedTask implements Task {
|
||||||
|
|
||||||
private FeedRefreshContext context;
|
private FeedRefreshContext context;
|
||||||
@@ -90,17 +88,21 @@ public class FeedRefreshWorker {
|
|||||||
int refreshInterval = applicationSettingsService.get().getRefreshIntervalMinutes();
|
int refreshInterval = applicationSettingsService.get().getRefreshIntervalMinutes();
|
||||||
Date disabledUntil = DateUtils.addMinutes(new Date(), refreshInterval);
|
Date disabledUntil = DateUtils.addMinutes(new Date(), refreshInterval);
|
||||||
try {
|
try {
|
||||||
FetchedFeed fetchedFeed = fetcher.fetch(feed.getUrl(), false, feed.getLastModifiedHeader(), feed.getEtagHeader(),
|
String url = ObjectUtils.firstNonNull(feed.getUrlAfterRedirect(), feed.getUrl());
|
||||||
|
FetchedFeed fetchedFeed = fetcher.fetch(url, false, feed.getLastModifiedHeader(), feed.getEtagHeader(),
|
||||||
feed.getLastPublishedDate(), feed.getLastContentHash());
|
feed.getLastPublishedDate(), feed.getLastContentHash());
|
||||||
// stops here if NotModifiedException or any other exception is
|
// stops here if NotModifiedException or any other exception is thrown
|
||||||
// thrown
|
|
||||||
List<FeedEntry> entries = fetchedFeed.getEntries();
|
List<FeedEntry> entries = fetchedFeed.getEntries();
|
||||||
|
|
||||||
if (applicationSettingsService.get().isHeavyLoad()) {
|
if (applicationSettingsService.get().isHeavyLoad()) {
|
||||||
disabledUntil = FeedUtils.buildDisabledUntil(fetchedFeed.getFeed().getLastEntryDate(), fetchedFeed.getFeed()
|
disabledUntil = FeedUtils.buildDisabledUntil(fetchedFeed.getFeed().getLastEntryDate(), fetchedFeed.getFeed()
|
||||||
.getAverageEntryInterval(), disabledUntil);
|
.getAverageEntryInterval(), disabledUntil);
|
||||||
}
|
}
|
||||||
|
String urlAfterRedirect = fetchedFeed.getUrlAfterRedirect();
|
||||||
|
if (StringUtils.equals(url, urlAfterRedirect)) {
|
||||||
|
urlAfterRedirect = null;
|
||||||
|
}
|
||||||
|
feed.setUrlAfterRedirect(urlAfterRedirect);
|
||||||
feed.setLink(fetchedFeed.getFeed().getLink());
|
feed.setLink(fetchedFeed.getFeed().getLink());
|
||||||
feed.setLastModifiedHeader(fetchedFeed.getFeed().getLastModifiedHeader());
|
feed.setLastModifiedHeader(fetchedFeed.getFeed().getLastModifiedHeader());
|
||||||
feed.setEtagHeader(fetchedFeed.getFeed().getEtagHeader());
|
feed.setEtagHeader(fetchedFeed.getFeed().getEtagHeader());
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ public class FetchedFeed {
|
|||||||
private List<FeedEntry> entries = Lists.newArrayList();
|
private List<FeedEntry> entries = Lists.newArrayList();
|
||||||
|
|
||||||
private String title;
|
private String title;
|
||||||
|
private String urlAfterRedirect;
|
||||||
private long fetchDuration;
|
private long fetchDuration;
|
||||||
|
|
||||||
public Feed getFeed() {
|
public Feed getFeed() {
|
||||||
@@ -45,4 +46,13 @@ public class FetchedFeed {
|
|||||||
public void setFetchDuration(long fetchDuration) {
|
public void setFetchDuration(long fetchDuration) {
|
||||||
this.fetchDuration = fetchDuration;
|
this.fetchDuration = fetchDuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getUrlAfterRedirect() {
|
||||||
|
return urlAfterRedirect;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUrlAfterRedirect(String urlAfterRedirect) {
|
||||||
|
this.urlAfterRedirect = urlAfterRedirect;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.commafeed.backend.metrics;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import javax.enterprise.context.ApplicationScoped;
|
||||||
|
import javax.enterprise.inject.Produces;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import com.codahale.metrics.JmxReporter;
|
||||||
|
import com.codahale.metrics.MetricRegistry;
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
@Slf4j
|
||||||
|
public class MetricRegistryProducer {
|
||||||
|
|
||||||
|
private MetricRegistry registry;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
private void init() {
|
||||||
|
log.info("initializing metrics registry");
|
||||||
|
registry = new MetricRegistry();
|
||||||
|
JmxReporter.forRegistry(registry).build().start();
|
||||||
|
log.info("metrics registry initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Produces
|
||||||
|
public MetricRegistry produceMetricsRegistry() {
|
||||||
|
return registry;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,6 +33,12 @@ public class Feed extends AbstractModel {
|
|||||||
@Column(length = 2048, nullable = false)
|
@Column(length = 2048, nullable = false)
|
||||||
private String url;
|
private String url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cache the url after potential http 30x redirects
|
||||||
|
*/
|
||||||
|
@Column(name = "url_after_redirect", length = 2048, nullable = false)
|
||||||
|
private String urlAfterRedirect;
|
||||||
|
|
||||||
@Column(length = 2048, nullable = false)
|
@Column(length = 2048, nullable = false)
|
||||||
private String normalizedUrl;
|
private String normalizedUrl;
|
||||||
|
|
||||||
@@ -130,11 +136,4 @@ public class Feed extends AbstractModel {
|
|||||||
@Temporal(TemporalType.TIMESTAMP)
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
private Date pushLastPing;
|
private Date pushLastPing;
|
||||||
|
|
||||||
public Feed() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public Feed(String url) {
|
|
||||||
this.url = url;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.commafeed.backend.feeds;
|
package com.commafeed.backend.opml;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.commafeed.backend.feeds;
|
package com.commafeed.backend.opml;
|
||||||
|
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -11,10 +11,12 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
import com.commafeed.backend.cache.CacheService;
|
import com.commafeed.backend.cache.CacheService;
|
||||||
import com.commafeed.backend.dao.FeedCategoryDAO;
|
import com.commafeed.backend.dao.FeedCategoryDAO;
|
||||||
|
import com.commafeed.backend.feeds.FeedUtils;
|
||||||
import com.commafeed.backend.model.FeedCategory;
|
import com.commafeed.backend.model.FeedCategory;
|
||||||
import com.commafeed.backend.model.User;
|
import com.commafeed.backend.model.User;
|
||||||
import com.commafeed.backend.services.FeedSubscriptionService;
|
import com.commafeed.backend.services.FeedSubscriptionService;
|
||||||
@@ -56,8 +58,8 @@ public class OPMLImporter {
|
|||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private void handleOutline(User user, Outline outline, FeedCategory parent) {
|
private void handleOutline(User user, Outline outline, FeedCategory parent) {
|
||||||
|
List<Outline> children = outline.getChildren();
|
||||||
if (StringUtils.isEmpty(outline.getType())) {
|
if (CollectionUtils.isNotEmpty(children)) {
|
||||||
String name = FeedUtils.truncate(outline.getText(), 128);
|
String name = FeedUtils.truncate(outline.getText(), 128);
|
||||||
if (name == null) {
|
if (name == null) {
|
||||||
name = FeedUtils.truncate(outline.getTitle(), 128);
|
name = FeedUtils.truncate(outline.getTitle(), 128);
|
||||||
@@ -75,7 +77,6 @@ public class OPMLImporter {
|
|||||||
feedCategoryDAO.saveOrUpdate(category);
|
feedCategoryDAO.saveOrUpdate(category);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Outline> children = outline.getChildren();
|
|
||||||
for (Outline child : children) {
|
for (Outline child : children) {
|
||||||
handleOutline(user, child, category);
|
handleOutline(user, child, category);
|
||||||
}
|
}
|
||||||
@@ -87,7 +88,7 @@ public class OPMLImporter {
|
|||||||
if (StringUtils.isBlank(name)) {
|
if (StringUtils.isBlank(name)) {
|
||||||
name = "Unnamed subscription";
|
name = "Unnamed subscription";
|
||||||
}
|
}
|
||||||
// make sure we continue with the import process even a feed failed
|
// make sure we continue with the import process even if a feed failed
|
||||||
try {
|
try {
|
||||||
feedSubscriptionService.subscribe(user, outline.getXmlUrl(), name, parent);
|
feedSubscriptionService.subscribe(user, outline.getXmlUrl(), name, parent);
|
||||||
} catch (FeedSubscriptionException e) {
|
} catch (FeedSubscriptionException e) {
|
||||||
@@ -3,7 +3,8 @@ package com.commafeed.backend.services;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
|
|
||||||
import javax.ejb.Singleton;
|
import javax.annotation.PostConstruct;
|
||||||
|
import javax.enterprise.context.ApplicationScoped;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import org.apache.commons.lang.time.DateUtils;
|
import org.apache.commons.lang.time.DateUtils;
|
||||||
@@ -15,7 +16,7 @@ import com.commafeed.backend.dao.ApplicationSettingsDAO;
|
|||||||
import com.commafeed.backend.model.ApplicationSettings;
|
import com.commafeed.backend.model.ApplicationSettings;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
@Singleton
|
@ApplicationScoped
|
||||||
public class ApplicationSettingsService {
|
public class ApplicationSettingsService {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -23,19 +24,21 @@ public class ApplicationSettingsService {
|
|||||||
|
|
||||||
private ApplicationSettings settings;
|
private ApplicationSettings settings;
|
||||||
|
|
||||||
public void save(ApplicationSettings settings) {
|
@PostConstruct
|
||||||
this.settings = settings;
|
private void init() {
|
||||||
applicationSettingsDAO.saveOrUpdate(settings);
|
settings = Iterables.getFirst(applicationSettingsDAO.findAll(), null);
|
||||||
applyLogLevel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ApplicationSettings get() {
|
public ApplicationSettings get() {
|
||||||
if (settings == null) {
|
|
||||||
settings = Iterables.getFirst(applicationSettingsDAO.findAll(), null);
|
|
||||||
}
|
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void save(ApplicationSettings settings) {
|
||||||
|
applicationSettingsDAO.saveOrUpdate(settings);
|
||||||
|
this.settings = settings;
|
||||||
|
applyLogLevel();
|
||||||
|
}
|
||||||
|
|
||||||
public Date getUnreadThreshold() {
|
public Date getUnreadThreshold() {
|
||||||
int keepStatusDays = get().getKeepStatusDays();
|
int keepStatusDays = get().getKeepStatusDays();
|
||||||
return keepStatusDays > 0 ? DateUtils.addDays(new Date(), -1 * keepStatusDays) : null;
|
return keepStatusDays > 0 ? DateUtils.addDays(new Date(), -1 * keepStatusDays) : null;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.commafeed.backend;
|
package com.commafeed.backend.services;
|
||||||
|
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -15,15 +16,15 @@ import com.commafeed.backend.dao.FeedEntryDAO;
|
|||||||
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.Feed;
|
import com.commafeed.backend.model.Feed;
|
||||||
|
import com.commafeed.backend.model.FeedEntryStatus;
|
||||||
import com.commafeed.backend.model.FeedSubscription;
|
import com.commafeed.backend.model.FeedSubscription;
|
||||||
import com.commafeed.backend.services.ApplicationSettingsService;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains utility methods for cleaning the database
|
* Contains utility methods for cleaning the database
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class DatabaseCleaner {
|
public class DatabaseCleaningService {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FeedDAO feedDAO;
|
FeedDAO feedDAO;
|
||||||
@@ -99,9 +100,19 @@ public class DatabaseCleaner {
|
|||||||
feedDAO.saveOrUpdate(into);
|
feedDAO.saveOrUpdate(into);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cleanStatusesOlderThan(Date olderThan) {
|
public long cleanStatusesOlderThan(Date olderThan) {
|
||||||
log.info("cleaning old read statuses");
|
log.info("cleaning old read statuses");
|
||||||
int deleted = feedEntryStatusDAO.deleteOldStatuses(olderThan);
|
long total = 0;
|
||||||
log.info("cleaned {} read statuses", deleted);
|
List<FeedEntryStatus> list = Collections.emptyList();
|
||||||
|
do {
|
||||||
|
list = feedEntryStatusDAO.getOldStatuses(olderThan, 100);
|
||||||
|
if (!list.isEmpty()) {
|
||||||
|
feedEntryStatusDAO.delete(list);
|
||||||
|
total += list.size();
|
||||||
|
log.info("cleaned {} old read statuses", total);
|
||||||
|
}
|
||||||
|
} while (!list.isEmpty());
|
||||||
|
log.info("cleanup done: {} old read statuses deleted", total);
|
||||||
|
return total;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.commafeed.backend;
|
package com.commafeed.backend.startup;
|
||||||
|
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.commafeed.backend;
|
package com.commafeed.backend.startup;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
@@ -10,6 +10,8 @@ import com.commafeed.backend.services.UserService;
|
|||||||
// extend Component in order to benefit from injection
|
// extend Component in order to benefit from injection
|
||||||
public class CommaFeedSessionServices extends Component {
|
public class CommaFeedSessionServices extends Component {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
UserService userService;
|
UserService userService;
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import org.apache.wicket.markup.html.TransparentWebMarkupContainer;
|
|||||||
import org.apache.wicket.markup.html.WebMarkupContainer;
|
import org.apache.wicket.markup.html.WebMarkupContainer;
|
||||||
import org.apache.wicket.markup.html.WebPage;
|
import org.apache.wicket.markup.html.WebPage;
|
||||||
|
|
||||||
import com.commafeed.backend.StartupBean;
|
|
||||||
import com.commafeed.backend.dao.FeedCategoryDAO;
|
import com.commafeed.backend.dao.FeedCategoryDAO;
|
||||||
import com.commafeed.backend.dao.FeedDAO;
|
import com.commafeed.backend.dao.FeedDAO;
|
||||||
import com.commafeed.backend.dao.FeedEntryDAO;
|
import com.commafeed.backend.dao.FeedEntryDAO;
|
||||||
@@ -29,6 +28,7 @@ import com.commafeed.backend.model.User;
|
|||||||
import com.commafeed.backend.model.UserSettings;
|
import com.commafeed.backend.model.UserSettings;
|
||||||
import com.commafeed.backend.services.ApplicationSettingsService;
|
import com.commafeed.backend.services.ApplicationSettingsService;
|
||||||
import com.commafeed.backend.services.MailService;
|
import com.commafeed.backend.services.MailService;
|
||||||
|
import com.commafeed.backend.startup.StartupBean;
|
||||||
import com.commafeed.frontend.CommaFeedSession;
|
import com.commafeed.frontend.CommaFeedSession;
|
||||||
import com.commafeed.frontend.utils.WicketUtils;
|
import com.commafeed.frontend.utils.WicketUtils;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import org.apache.wicket.markup.html.WebPage;
|
import org.apache.wicket.markup.html.WebPage;
|
||||||
|
|
||||||
import com.commafeed.backend.StartupBean;
|
|
||||||
import com.commafeed.backend.services.UserService;
|
import com.commafeed.backend.services.UserService;
|
||||||
|
import com.commafeed.backend.startup.StartupBean;
|
||||||
import com.commafeed.frontend.CommaFeedSession;
|
import com.commafeed.frontend.CommaFeedSession;
|
||||||
|
|
||||||
public class DemoLoginPage extends WebPage {
|
public class DemoLoginPage extends WebPage {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import org.apache.wicket.markup.head.CssHeaderItem;
|
|||||||
import org.apache.wicket.markup.head.IHeaderResponse;
|
import org.apache.wicket.markup.head.IHeaderResponse;
|
||||||
import org.apache.wicket.request.mapper.parameter.PageParameters;
|
import org.apache.wicket.request.mapper.parameter.PageParameters;
|
||||||
|
|
||||||
|
import com.commafeed.backend.model.User;
|
||||||
import com.commafeed.backend.model.UserRole.Role;
|
import com.commafeed.backend.model.UserRole.Role;
|
||||||
import com.commafeed.backend.model.UserSettings;
|
import com.commafeed.backend.model.UserSettings;
|
||||||
import com.commafeed.frontend.CommaFeedSession;
|
import com.commafeed.frontend.CommaFeedSession;
|
||||||
@@ -21,7 +22,11 @@ public class HomePage extends BasePage {
|
|||||||
response.render(CssHeaderItem.forReference(new UserCustomCssReference() {
|
response.render(CssHeaderItem.forReference(new UserCustomCssReference() {
|
||||||
@Override
|
@Override
|
||||||
protected String getCss() {
|
protected String getCss() {
|
||||||
UserSettings settings = userSettingsDAO.findByUser(CommaFeedSession.get().getUser());
|
User user = CommaFeedSession.get().getUser();
|
||||||
|
if (user == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
UserSettings settings = userSettingsDAO.findByUser(user);
|
||||||
return settings == null ? null : settings.getCustomCss();
|
return settings == null ? null : settings.getCustomCss();
|
||||||
}
|
}
|
||||||
}, new PageParameters().add("_t", System.currentTimeMillis()), null));
|
}, new PageParameters().add("_t", System.currentTimeMillis()), null));
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ import java.io.OutputStream;
|
|||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.WebApplicationException;
|
import javax.ws.rs.WebApplicationException;
|
||||||
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.ext.MessageBodyReader;
|
import javax.ws.rs.ext.MessageBodyReader;
|
||||||
@@ -19,6 +21,7 @@ import org.apache.http.HttpHeaders;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectWriter;
|
||||||
|
|
||||||
@Provider
|
@Provider
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
@@ -30,6 +33,9 @@ public class JsonProvider implements MessageBodyReader<Object>, MessageBodyWrite
|
|||||||
|
|
||||||
private static final ObjectMapper MAPPER = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
private static final ObjectMapper MAPPER = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||||
|
|
||||||
|
@Context
|
||||||
|
private HttpServletRequest request;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(Object value, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
|
public void writeTo(Object value, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
|
||||||
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException {
|
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException {
|
||||||
@@ -38,10 +44,29 @@ public class JsonProvider implements MessageBodyReader<Object>, MessageBodyWrite
|
|||||||
httpHeaders.putSingle(HttpHeaders.CACHE_CONTROL, CACHE_CONTROL_VALUE);
|
httpHeaders.putSingle(HttpHeaders.CACHE_CONTROL, CACHE_CONTROL_VALUE);
|
||||||
httpHeaders.putSingle(HttpHeaders.PRAGMA, CACHE_CONTROL_VALUE);
|
httpHeaders.putSingle(HttpHeaders.PRAGMA, CACHE_CONTROL_VALUE);
|
||||||
|
|
||||||
getMapper().writeValue(entityStream, value);
|
ObjectWriter writer = getMapper().writer();
|
||||||
|
if (hasPrettyPrint(annotations)) {
|
||||||
|
writer = writer.withDefaultPrettyPrinter();
|
||||||
|
}
|
||||||
|
writer.writeValue(entityStream, value);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean hasPrettyPrint(Annotation[] annotations) {
|
||||||
|
boolean prettyPrint = false;
|
||||||
|
|
||||||
|
for (Annotation annotation : annotations) {
|
||||||
|
if (PrettyPrint.class.equals(annotation.annotationType())) {
|
||||||
|
prettyPrint = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!prettyPrint && request != null) {
|
||||||
|
prettyPrint = Boolean.parseBoolean(request.getParameter("pretty"));
|
||||||
|
}
|
||||||
|
return prettyPrint;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType,
|
public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType,
|
||||||
MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException {
|
MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException {
|
||||||
|
|||||||
12
src/main/java/com/commafeed/frontend/rest/PrettyPrint.java
Normal file
12
src/main/java/com/commafeed/frontend/rest/PrettyPrint.java
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package com.commafeed.frontend.rest;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Target({ ElementType.METHOD })
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface PrettyPrint {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ import org.apache.wicket.protocol.http.servlet.ServletWebResponse;
|
|||||||
import org.apache.wicket.request.cycle.RequestCycle;
|
import org.apache.wicket.request.cycle.RequestCycle;
|
||||||
import org.apache.wicket.util.crypt.Base64;
|
import org.apache.wicket.util.crypt.Base64;
|
||||||
|
|
||||||
|
import com.codahale.metrics.MetricRegistry;
|
||||||
import com.commafeed.backend.dao.UserDAO;
|
import com.commafeed.backend.dao.UserDAO;
|
||||||
import com.commafeed.backend.model.User;
|
import com.commafeed.backend.model.User;
|
||||||
import com.commafeed.backend.model.UserRole.Role;
|
import com.commafeed.backend.model.UserRole.Role;
|
||||||
@@ -42,6 +43,9 @@ public abstract class AbstractREST {
|
|||||||
@Context
|
@Context
|
||||||
private HttpServletResponse response;
|
private HttpServletResponse response;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
MetricRegistry metrics;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private UserDAO userDAO;
|
private UserDAO userDAO;
|
||||||
|
|
||||||
@@ -93,10 +97,13 @@ public abstract class AbstractREST {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@AroundInvoke
|
@AroundInvoke
|
||||||
public Object checkSecurity(InvocationContext context) throws Exception {
|
public Object intercept(InvocationContext context) throws Exception {
|
||||||
|
Method method = context.getMethod();
|
||||||
|
|
||||||
|
// check security
|
||||||
boolean allowed = true;
|
boolean allowed = true;
|
||||||
User user = null;
|
User user = null;
|
||||||
Method method = context.getMethod();
|
|
||||||
SecurityCheck check = method.isAnnotationPresent(SecurityCheck.class) ? method.getAnnotation(SecurityCheck.class) : method
|
SecurityCheck check = method.isAnnotationPresent(SecurityCheck.class) ? method.getAnnotation(SecurityCheck.class) : method
|
||||||
.getDeclaringClass().getAnnotation(SecurityCheck.class);
|
.getDeclaringClass().getAnnotation(SecurityCheck.class);
|
||||||
|
|
||||||
@@ -118,7 +125,16 @@ public abstract class AbstractREST {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return context.proceed();
|
Object result = null;
|
||||||
|
com.codahale.metrics.Timer.Context timer = metrics.timer(
|
||||||
|
MetricRegistry.name(method.getDeclaringClass(), method.getName(), "responseTime")).time();
|
||||||
|
try {
|
||||||
|
result = context.proceed();
|
||||||
|
} finally {
|
||||||
|
timer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkRole(Role requiredRole) {
|
private boolean checkRole(Role requiredRole) {
|
||||||
|
|||||||
@@ -17,9 +17,7 @@ import javax.ws.rs.core.Response.Status;
|
|||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
import com.commafeed.backend.DatabaseCleaner;
|
import com.codahale.metrics.MetricRegistry;
|
||||||
import com.commafeed.backend.MetricsBean;
|
|
||||||
import com.commafeed.backend.StartupBean;
|
|
||||||
import com.commafeed.backend.dao.FeedDAO;
|
import com.commafeed.backend.dao.FeedDAO;
|
||||||
import com.commafeed.backend.dao.FeedDAO.DuplicateMode;
|
import com.commafeed.backend.dao.FeedDAO.DuplicateMode;
|
||||||
import com.commafeed.backend.dao.UserDAO;
|
import com.commafeed.backend.dao.UserDAO;
|
||||||
@@ -33,14 +31,17 @@ import com.commafeed.backend.model.User;
|
|||||||
import com.commafeed.backend.model.UserRole;
|
import com.commafeed.backend.model.UserRole;
|
||||||
import com.commafeed.backend.model.UserRole.Role;
|
import com.commafeed.backend.model.UserRole.Role;
|
||||||
import com.commafeed.backend.services.ApplicationSettingsService;
|
import com.commafeed.backend.services.ApplicationSettingsService;
|
||||||
|
import com.commafeed.backend.services.DatabaseCleaningService;
|
||||||
import com.commafeed.backend.services.FeedService;
|
import com.commafeed.backend.services.FeedService;
|
||||||
import com.commafeed.backend.services.PasswordEncryptionService;
|
import com.commafeed.backend.services.PasswordEncryptionService;
|
||||||
import com.commafeed.backend.services.UserService;
|
import com.commafeed.backend.services.UserService;
|
||||||
|
import com.commafeed.backend.startup.StartupBean;
|
||||||
import com.commafeed.frontend.SecurityCheck;
|
import com.commafeed.frontend.SecurityCheck;
|
||||||
import com.commafeed.frontend.model.FeedCount;
|
import com.commafeed.frontend.model.FeedCount;
|
||||||
import com.commafeed.frontend.model.UserModel;
|
import com.commafeed.frontend.model.UserModel;
|
||||||
import com.commafeed.frontend.model.request.FeedMergeRequest;
|
import com.commafeed.frontend.model.request.FeedMergeRequest;
|
||||||
import com.commafeed.frontend.model.request.IDRequest;
|
import com.commafeed.frontend.model.request.IDRequest;
|
||||||
|
import com.commafeed.frontend.rest.PrettyPrint;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
@@ -70,10 +71,10 @@ public class AdminREST extends AbstractREST {
|
|||||||
FeedDAO feedDAO;
|
FeedDAO feedDAO;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
MetricsBean metricsBean;
|
MetricRegistry metrics;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
DatabaseCleaner cleaner;
|
DatabaseCleaningService cleaner;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FeedRefreshWorker feedRefreshWorker;
|
FeedRefreshWorker feedRefreshWorker;
|
||||||
@@ -226,21 +227,10 @@ public class AdminREST extends AbstractREST {
|
|||||||
|
|
||||||
@Path("/metrics")
|
@Path("/metrics")
|
||||||
@GET
|
@GET
|
||||||
|
@PrettyPrint
|
||||||
@ApiOperation(value = "Retrieve server metrics")
|
@ApiOperation(value = "Retrieve server metrics")
|
||||||
public Response getMetrics(@QueryParam("backlog") @DefaultValue("false") boolean backlog) {
|
public Response getMetrics() {
|
||||||
Map<String, Object> map = Maps.newLinkedHashMap();
|
return Response.ok(metrics).build();
|
||||||
map.put("lastMinute", metricsBean.getLastMinute());
|
|
||||||
map.put("lastHour", metricsBean.getLastHour());
|
|
||||||
if (backlog) {
|
|
||||||
map.put("backlog", taskGiver.getUpdatableCount());
|
|
||||||
}
|
|
||||||
map.put("http_active", feedRefreshWorker.getActiveCount());
|
|
||||||
map.put("http_queue", feedRefreshWorker.getQueueSize());
|
|
||||||
map.put("database_active", feedRefreshUpdater.getActiveCount());
|
|
||||||
map.put("database_queue", feedRefreshUpdater.getQueueSize());
|
|
||||||
map.put("cache", metricsBean.getCacheStats());
|
|
||||||
|
|
||||||
return Response.ok(map).build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("/cleanup/feeds")
|
@Path("/cleanup/feeds")
|
||||||
|
|||||||
@@ -170,8 +170,9 @@ public class CategoryREST extends AbstractREST {
|
|||||||
.isImageProxyEnabled()));
|
.isImageProxyEnabled()));
|
||||||
}
|
}
|
||||||
entries.setName(parent.getName());
|
entries.setName(parent.getName());
|
||||||
|
} else {
|
||||||
|
return Response.status(Status.NOT_FOUND).entity("<message>category not found</message>").build();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasMore = entries.getEntries().size() > limit;
|
boolean hasMore = entries.getEntries().size() > limit;
|
||||||
@@ -200,7 +201,11 @@ 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, null, false, null).getEntity();
|
Response response = getCategoryEntries(id, readType, null, offset, limit, order, null, false, null);
|
||||||
|
if (response.getStatus() != Status.OK.getStatusCode()) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
Entries entries = (Entries) response.getEntity();
|
||||||
|
|
||||||
SyndFeed feed = new SyndFeedImpl();
|
SyndFeed feed = new SyndFeedImpl();
|
||||||
feed.setFeedType("rss_2.0");
|
feed.setFeedType("rss_2.0");
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ import org.apache.commons.io.IOUtils;
|
|||||||
import org.apache.commons.lang.ObjectUtils;
|
import org.apache.commons.lang.ObjectUtils;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
import com.commafeed.backend.StartupBean;
|
|
||||||
import com.commafeed.backend.cache.CacheService;
|
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;
|
||||||
@@ -47,8 +46,6 @@ import com.commafeed.backend.feeds.FeedFetcher;
|
|||||||
import com.commafeed.backend.feeds.FeedRefreshTaskGiver;
|
import com.commafeed.backend.feeds.FeedRefreshTaskGiver;
|
||||||
import com.commafeed.backend.feeds.FeedUtils;
|
import com.commafeed.backend.feeds.FeedUtils;
|
||||||
import com.commafeed.backend.feeds.FetchedFeed;
|
import com.commafeed.backend.feeds.FetchedFeed;
|
||||||
import com.commafeed.backend.feeds.OPMLExporter;
|
|
||||||
import com.commafeed.backend.feeds.OPMLImporter;
|
|
||||||
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.FeedEntryStatus;
|
import com.commafeed.backend.model.FeedEntryStatus;
|
||||||
@@ -56,9 +53,12 @@ import com.commafeed.backend.model.FeedSubscription;
|
|||||||
import com.commafeed.backend.model.UserRole.Role;
|
import com.commafeed.backend.model.UserRole.Role;
|
||||||
import com.commafeed.backend.model.UserSettings.ReadingMode;
|
import com.commafeed.backend.model.UserSettings.ReadingMode;
|
||||||
import com.commafeed.backend.model.UserSettings.ReadingOrder;
|
import com.commafeed.backend.model.UserSettings.ReadingOrder;
|
||||||
|
import com.commafeed.backend.opml.OPMLExporter;
|
||||||
|
import com.commafeed.backend.opml.OPMLImporter;
|
||||||
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.backend.services.FeedSubscriptionService;
|
import com.commafeed.backend.services.FeedSubscriptionService;
|
||||||
|
import com.commafeed.backend.startup.StartupBean;
|
||||||
import com.commafeed.frontend.SecurityCheck;
|
import com.commafeed.frontend.SecurityCheck;
|
||||||
import com.commafeed.frontend.model.Entries;
|
import com.commafeed.frontend.model.Entries;
|
||||||
import com.commafeed.frontend.model.Entry;
|
import com.commafeed.frontend.model.Entry;
|
||||||
@@ -181,6 +181,8 @@ public class FeedREST extends AbstractREST {
|
|||||||
entries.setHasMore(true);
|
entries.setHasMore(true);
|
||||||
entries.getEntries().remove(entries.getEntries().size() - 1);
|
entries.getEntries().remove(entries.getEntries().size() - 1);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return Response.status(Status.NOT_FOUND).entity("<message>feed not found</message>").build();
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.setTimestamp(System.currentTimeMillis());
|
entries.setTimestamp(System.currentTimeMillis());
|
||||||
@@ -202,7 +204,11 @@ 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, null, false).getEntity();
|
Response response = getFeedEntries(id, readType, null, offset, limit, order, null, false);
|
||||||
|
if (response.getStatus() != Status.OK.getStatusCode()) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
Entries entries = (Entries) response.getEntity();
|
||||||
|
|
||||||
SyndFeed feed = new SyndFeedImpl();
|
SyndFeed feed = new SyndFeedImpl();
|
||||||
feed.setFeedType("rss_2.0");
|
feed.setFeedType("rss_2.0");
|
||||||
@@ -235,7 +241,7 @@ public class FeedREST extends AbstractREST {
|
|||||||
try {
|
try {
|
||||||
FetchedFeed feed = feedFetcher.fetch(url, true, null, null, null, null);
|
FetchedFeed feed = feedFetcher.fetch(url, true, null, null, null, null);
|
||||||
info = new FeedInfo();
|
info = new FeedInfo();
|
||||||
info.setUrl(feed.getFeed().getUrl());
|
info.setUrl(feed.getUrlAfterRedirect());
|
||||||
info.setTitle(feed.getTitle());
|
info.setTitle(feed.getTitle());
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.commafeed.frontend.rest.resources;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
@@ -22,7 +23,8 @@ import org.apache.commons.io.IOUtils;
|
|||||||
import org.apache.commons.lang3.ArrayUtils;
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import com.commafeed.backend.MetricsBean;
|
import com.codahale.metrics.Meter;
|
||||||
|
import com.codahale.metrics.MetricRegistry;
|
||||||
import com.commafeed.backend.dao.FeedDAO;
|
import com.commafeed.backend.dao.FeedDAO;
|
||||||
import com.commafeed.backend.feeds.FeedParser;
|
import com.commafeed.backend.feeds.FeedParser;
|
||||||
import com.commafeed.backend.feeds.FeedRefreshTaskGiver;
|
import com.commafeed.backend.feeds.FeedRefreshTaskGiver;
|
||||||
@@ -52,8 +54,13 @@ public class PubSubHubbubCallbackREST extends AbstractREST {
|
|||||||
@Inject
|
@Inject
|
||||||
ApplicationSettingsService applicationSettingsService;
|
ApplicationSettingsService applicationSettingsService;
|
||||||
|
|
||||||
@Inject
|
private Meter pushReceived;
|
||||||
MetricsBean metricsBean;
|
|
||||||
|
@PostConstruct
|
||||||
|
public void initMetrics() {
|
||||||
|
pushReceived = metrics.meter(MetricRegistry.name(getClass(), "pushReceived"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Path("/callback")
|
@Path("/callback")
|
||||||
@GET
|
@GET
|
||||||
@@ -119,7 +126,7 @@ public class PubSubHubbubCallbackREST extends AbstractREST {
|
|||||||
log.debug("pushing content to queue for {}", feed.getUrl());
|
log.debug("pushing content to queue for {}", feed.getUrl());
|
||||||
taskGiver.add(feed, false);
|
taskGiver.add(feed, false);
|
||||||
}
|
}
|
||||||
metricsBean.pushReceived(feeds.size());
|
pushReceived.mark();
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Could not parse pubsub callback: " + e.getMessage());
|
log.error("Could not parse pubsub callback: " + e.getMessage());
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ import javax.ws.rs.core.Response.Status;
|
|||||||
|
|
||||||
import com.commafeed.backend.HttpGetter;
|
import com.commafeed.backend.HttpGetter;
|
||||||
import com.commafeed.backend.HttpGetter.HttpResult;
|
import com.commafeed.backend.HttpGetter.HttpResult;
|
||||||
import com.commafeed.backend.StartupBean;
|
|
||||||
import com.commafeed.backend.feeds.FeedUtils;
|
import com.commafeed.backend.feeds.FeedUtils;
|
||||||
import com.commafeed.backend.services.ApplicationPropertiesService;
|
import com.commafeed.backend.services.ApplicationPropertiesService;
|
||||||
import com.commafeed.backend.services.ApplicationSettingsService;
|
import com.commafeed.backend.services.ApplicationSettingsService;
|
||||||
|
import com.commafeed.backend.startup.StartupBean;
|
||||||
import com.commafeed.frontend.model.ServerInfo;
|
import com.commafeed.frontend.model.ServerInfo;
|
||||||
import com.wordnik.swagger.annotations.Api;
|
import com.wordnik.swagger.annotations.Api;
|
||||||
import com.wordnik.swagger.annotations.ApiOperation;
|
import com.wordnik.swagger.annotations.ApiOperation;
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import javax.ws.rs.core.Response.Status;
|
|||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
import com.commafeed.backend.StartupBean;
|
|
||||||
import com.commafeed.backend.dao.UserDAO;
|
import com.commafeed.backend.dao.UserDAO;
|
||||||
import com.commafeed.backend.dao.UserRoleDAO;
|
import com.commafeed.backend.dao.UserRoleDAO;
|
||||||
import com.commafeed.backend.dao.UserSettingsDAO;
|
import com.commafeed.backend.dao.UserSettingsDAO;
|
||||||
@@ -25,6 +24,7 @@ import com.commafeed.backend.model.UserSettings.ViewMode;
|
|||||||
import com.commafeed.backend.services.ApplicationSettingsService;
|
import com.commafeed.backend.services.ApplicationSettingsService;
|
||||||
import com.commafeed.backend.services.PasswordEncryptionService;
|
import com.commafeed.backend.services.PasswordEncryptionService;
|
||||||
import com.commafeed.backend.services.UserService;
|
import com.commafeed.backend.services.UserService;
|
||||||
|
import com.commafeed.backend.startup.StartupBean;
|
||||||
import com.commafeed.frontend.SecurityCheck;
|
import com.commafeed.frontend.SecurityCheck;
|
||||||
import com.commafeed.frontend.model.Settings;
|
import com.commafeed.frontend.model.Settings;
|
||||||
import com.commafeed.frontend.model.UserModel;
|
import com.commafeed.frontend.model.UserModel;
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ import java.util.Map;
|
|||||||
import java.util.SortedMap;
|
import java.util.SortedMap;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See http://en.wikipedia.org/wiki/URL_normalization for a reference Note: some
|
* 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
|
* parts of the code are adapted from: http://stackoverflow.com/a/4057470/405418
|
||||||
@@ -46,7 +48,7 @@ public class URLCanonicalizer {
|
|||||||
URL canonicalURL = new URL(UrlResolver.resolveUrl(context == null ? "" : context, href));
|
URL canonicalURL = new URL(UrlResolver.resolveUrl(context == null ? "" : context, href));
|
||||||
|
|
||||||
String host = canonicalURL.getHost().toLowerCase();
|
String host = canonicalURL.getHost().toLowerCase();
|
||||||
if (host == "") {
|
if (StringUtils.isBlank(host)) {
|
||||||
// This is an invalid Url.
|
// This is an invalid Url.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
11
src/main/resources/changelogs/db.changelog-1.3.xml
Normal file
11
src/main/resources/changelogs/db.changelog-1.3.xml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.0.xsd">
|
||||||
|
|
||||||
|
<changeSet author="athou" id="add-url-after-redirect">
|
||||||
|
<addColumn tableName="FEEDS">
|
||||||
|
<column name="url_after_redirect" type="VARCHAR(2048)" />
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
||||||
@@ -6,5 +6,6 @@
|
|||||||
<include file="changelogs/db.changelog-1.0.xml" />
|
<include file="changelogs/db.changelog-1.0.xml" />
|
||||||
<include file="changelogs/db.changelog-1.1.xml" />
|
<include file="changelogs/db.changelog-1.1.xml" />
|
||||||
<include file="changelogs/db.changelog-1.2.xml" />
|
<include file="changelogs/db.changelog-1.2.xml" />
|
||||||
|
<include file="changelogs/db.changelog-1.3.xml" />
|
||||||
|
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
@@ -17,10 +17,10 @@ subscribe.feed_url=URL Ffrwd
|
|||||||
subscribe.feed_name=Enw Ffrwd
|
subscribe.feed_name=Enw Ffrwd
|
||||||
subscribe.category=Categori
|
subscribe.category=Categori
|
||||||
|
|
||||||
import.google_reader_prefix=Gadawa i mi fewnforio dy ffrydiau o dy
|
import.google_reader_prefix=Gad i mi fewnforio dy ffrydiau o dy
|
||||||
import.google_reader_suffix= gyfrif.
|
import.google_reader_suffix= gyfrif.
|
||||||
import.google_download=Fel arall, lanlwytho dy ffeil tanysgrifiadau.xml
|
import.google_download=Fel arall, lanlwytha dy ffeil tanysgrifiadau.xml
|
||||||
import.google_download_link=Lawrlwytho fe yma.
|
import.google_download_link=Lawrlwytha fe yma.
|
||||||
import.xml_file=Ffeil OPML
|
import.xml_file=Ffeil OPML
|
||||||
|
|
||||||
new_category.name=Enw
|
new_category.name=Enw
|
||||||
@@ -31,14 +31,14 @@ toolbar.all=Popeth
|
|||||||
toolbar.previous_entry=Eitem blaenorol
|
toolbar.previous_entry=Eitem blaenorol
|
||||||
toolbar.next_entry=Eitem nesaf
|
toolbar.next_entry=Eitem nesaf
|
||||||
toolbar.refresh=Adnewyddu
|
toolbar.refresh=Adnewyddu
|
||||||
toolbar.refresh_all=Force refresh all my feeds ####### Needs translation
|
toolbar.refresh_all=Gorfodi ail-lwytho pob ffrwd
|
||||||
toolbar.sort_by_asc_desc=Trefnu yn ôl dyddiad
|
toolbar.sort_by_asc_desc=Trefnu yn ôl dyddiad
|
||||||
toolbar.titles_only=Teitlau yn unig
|
toolbar.titles_only=Teitlau yn unig
|
||||||
toolbar.expanded_view=Golygfa estynedig
|
toolbar.expanded_view=Golwg estynedig
|
||||||
toolbar.mark_all_as_read=Marcio popeth fel darllenwyd
|
toolbar.mark_all_as_read=Nodi'r cyfan fel wedi ei ddarllen
|
||||||
toolbar.mark_all_older_day=Eitemau sy'n hyn na diwrnod
|
toolbar.mark_all_older_day=Eitemau hyn na diwrnod
|
||||||
toolbar.mark_all_older_week=Eitemau sy'n hyn nag wythnos
|
toolbar.mark_all_older_week=Eitemau hyn nag wythnos
|
||||||
toolbar.mark_all_older_two_weeks=Eitemau sy'n hyn na phythefnos
|
toolbar.mark_all_older_two_weeks=Eitemau hyn na phythefnos
|
||||||
toolbar.settings=Gosodiadau
|
toolbar.settings=Gosodiadau
|
||||||
toolbar.profile=Proffil
|
toolbar.profile=Proffil
|
||||||
toolbar.admin=Gweinyddwr
|
toolbar.admin=Gweinyddwr
|
||||||
@@ -46,42 +46,42 @@ toolbar.about=Ynghylch
|
|||||||
toolbar.logout=Allgofnodi
|
toolbar.logout=Allgofnodi
|
||||||
toolbar.donate=Rhoddi
|
toolbar.donate=Rhoddi
|
||||||
|
|
||||||
view.entry_source=from ####### Needs translation
|
view.entry_source=o
|
||||||
view.entry_author=by ####### Needs translation
|
view.entry_author=gan
|
||||||
view.error_while_loading_feed=Gwall tra'n llwytho'r ffrwd
|
view.error_while_loading_feed=Gwall wrth lwytho'r ffrwd
|
||||||
view.keep_unread=Cadw fel heb ei darllen
|
view.keep_unread=Parhau i'w nodi fel heb ei ddarllen
|
||||||
view.no_unread_items=dim eitemau heb eu darllen
|
view.no_unread_items=: Dim eitemau heb eu darllen ###### Cynnwys y colon oherwydd gystrawen y cyd-destyn
|
||||||
view.mark_up_to_here=Mark as read up to here ####### Needs translation
|
view.mark_up_to_here=Nodi'r rhai hyd yma fel wedi eu darllen
|
||||||
view.search_for=searching for: ####### Needs translation
|
view.search_for=yn chwilio am:
|
||||||
view.no_search_results=No match found for the requested keywords ####### Needs translation
|
view.no_search_results=Ni chanfuwyd unrhyw beth gyda'r geiriau hynny
|
||||||
|
|
||||||
feedsearch.hint=Teipio tanysgrifiad...
|
feedsearch.hint=Rho'r tanysgrifiad...
|
||||||
feedsearch.help=Defnyddia'r dychwelwr i ddethol a saethau i lywio
|
feedsearch.help=Defnyddia'r dychwelwr i ddethol a saethau i lywio
|
||||||
feedsearch.result_prefix=Dy danysgrifiadau:
|
feedsearch.result_prefix=Dy danysgrifiadau:
|
||||||
|
|
||||||
settings.general=Cyffredinol
|
settings.general=Cyffredinol
|
||||||
settings.general.language=Iaith
|
settings.general.language=Iaith
|
||||||
settings.general.language.contribute=Cyfrannu gyda chyfieithiadau
|
settings.general.language.contribute=Cyfrannu drwy gyfieithu
|
||||||
settings.general.show_unread=Dangos ffrydiau a chategoriau gyda dim eitemau heb eu darllen
|
settings.general.show_unread=Dangos ffrydiau a chategoriau gyda dim eitemau heb eu darllen
|
||||||
settings.general.social_buttons=Dangos botymau rhannu
|
settings.general.social_buttons=Dangos botymau rhannu
|
||||||
settings.general.scroll_marks=Mewn golygfa estynedig, sgrolio trwy eitemau yn marcio fel darllenwyd
|
settings.general.scroll_marks=Marcio eitemau fel wedi eu darllen wrth sgrolio drwyddynt yn y golwg estynedig ###### Defnyddio gystrawen debyg i'r ddau uwch.
|
||||||
settings.appearance=Golygfa
|
settings.appearance=Golwg
|
||||||
settings.theme=Thema
|
settings.theme=Thema
|
||||||
settings.submit_your_theme=Cyflwyno dy thema
|
settings.submit_your_theme=Cyflwyna dy thema
|
||||||
settings.custom_css=CSS wedi'i addasu
|
settings.custom_css=CSS wedi'i addasu
|
||||||
|
|
||||||
details.feed_details=Manylion ffrwd
|
details.feed_details=Manylion ffrwd
|
||||||
details.url=URL
|
details.url=URL
|
||||||
details.website=Website ####### Needs translation
|
details.website=Gwefan
|
||||||
details.name=Enw
|
details.name=Enw
|
||||||
details.category=Categori
|
details.category=Categori
|
||||||
details.position=Safle
|
details.position=Safle
|
||||||
details.last_refresh=Adnewyddiad diwethaf
|
details.last_refresh=Adnewyddiad diwethaf
|
||||||
details.message=Last refresh message ####### Needs translation
|
details.message=Neges adnewyddiad diwethaf
|
||||||
details.next_refresh=Adnewyddiad nesaf
|
details.next_refresh=Adnewyddiad nesaf
|
||||||
details.queued_for_refresh=Ciwiwyd am adnewyddu
|
details.queued_for_refresh=Ciwiwyd i'w adnewyddu
|
||||||
details.feed_url=URL Ffrwd
|
details.feed_url=URL Ffrwd
|
||||||
details.generate_api_key_first=Cynhyrchu allwedd API yn dy broffil yn gyntaf.
|
details.generate_api_key_first=Rhaid creu allwedd API yn dy broffil yn gyntaf.
|
||||||
details.unsubscribe=Dad-danysgrifio
|
details.unsubscribe=Dad-danysgrifio
|
||||||
details.category_details=Manylion categori
|
details.category_details=Manylion categori
|
||||||
details.parent_category=Categori rhiant
|
details.parent_category=Categori rhiant
|
||||||
@@ -91,59 +91,59 @@ profile.email=E-bost
|
|||||||
profile.change_password=Newid cyfrinair
|
profile.change_password=Newid cyfrinair
|
||||||
profile.confirm_password=Cadarnhau cyfrinair
|
profile.confirm_password=Cadarnhau cyfrinair
|
||||||
profile.minimum_6_chars=Isafswm 6 nod
|
profile.minimum_6_chars=Isafswm 6 nod
|
||||||
profile.passwords_do_not_match=Cyfrineiriau yn wahanol
|
profile.passwords_do_not_match=Mae'r cyfrineiriau yn wahanol
|
||||||
profile.api_key=allwedd API
|
profile.api_key=Allwedd API
|
||||||
profile.api_key_not_generated=Heb gynhyrchu eto
|
profile.api_key_not_generated=Heb ei gynhyrchu eto
|
||||||
profile.generate_new_api_key=Cynhyrchu allwedd API newydd
|
profile.generate_new_api_key=Creu allwedd API newydd
|
||||||
profile.generate_new_api_key_info=Newid cyfrinair yn cynhyrchu allwedd API newydd
|
profile.generate_new_api_key_info=Mae newid cyfrinair yn creu allwedd API newydd
|
||||||
profile.opml_export=Allforio OPML
|
profile.opml_export=Allforio OPML
|
||||||
profile.delete_account=Dileu cyfrif
|
profile.delete_account=Dileu cyfrif
|
||||||
|
|
||||||
about.rest_api=REST API
|
about.rest_api=REST API
|
||||||
about.keyboard_shortcuts=Llwybr byr bysellfwrdd
|
about.keyboard_shortcuts=Llwybr byr bysellfwrdd
|
||||||
about.version=CommaFeed version ####### Needs translation
|
about.version=Fersiwn CommaFeed: ###### Cynnwys y colon oherwydd gystrawen y cyd-destun
|
||||||
about.line1_prefix=CommaFeed yn prosiect cod agored. Mae'r cod ar
|
about.line1_prefix=Mae CommaFeed yn prosiect cod agored. Mae'r cod ar
|
||||||
about.line1_suffix=.
|
about.line1_suffix=.
|
||||||
about.line2_prefix=Os wyt ti'n ffeindio problem, plis adrodda fe ar dudalen problemau o'r
|
about.line2_prefix=Os wyt ti'n ffeindio problem, plîs gad wybod amdano ar dudalen problemau o'r
|
||||||
about.line2_suffix=\ prosiect.
|
about.line2_suffix=\ prosiect.
|
||||||
about.line3=Os wyt ti'n hoffi'r prosiect, plis ystyried cyfraniad er mwyn cefnogi'r datblygwr a helpu gyda chynnal a chadw o'r wefan hon.
|
about.line3=Os wyt ti'n hoffi'r prosiect, plîs ystyria cyfrannu i gefnogi'r datblygwr a helpu gyda chynnal a chadw'r wefan hon.
|
||||||
about.line4=I'r rhai sy'n hoff o bitcoin, dyma'r gyfeiriad
|
about.line4=I'r rhai sy'n hoff o Bitcoin, dyma'r cyfeiriad
|
||||||
about.goodies=Goodies
|
about.goodies=Goodies
|
||||||
about.goodies.android_app=Android app ####### Needs translation
|
about.goodies.android_app=Ap Android
|
||||||
about.goodies.subscribe_url=URL Tanysgrifio
|
about.goodies.subscribe_url=URL Tanysgrifio
|
||||||
about.goodies.chrome_extension=estyniad Chrome
|
about.goodies.chrome_extension=estyniad Chrome
|
||||||
about.goodies.firefox_extension=estyniad Firefox
|
about.goodies.firefox_extension=estyniad Firefox
|
||||||
about.goodies.opera_extension=estyniad Opera
|
about.goodies.opera_extension=estyniad Opera
|
||||||
about.goodies.subscribe_bookmarklet=Ychwanegu botwm tanysgrifio (clicio)
|
about.goodies.subscribe_bookmarklet=Ychwanegu botwm tanysgrifio ###### Dim angen 'Click' - digon amlwg o'r cyd-destyn
|
||||||
about.goodies.subscribe_bookmarklet_asc=Oldest first ####### Needs translation
|
about.goodies.subscribe_bookmarklet_asc=Hynaf yn gyntaf
|
||||||
about.goodies.subscribe_bookmarklet_desc=Newest first ####### Needs translation
|
about.goodies.subscribe_bookmarklet_desc=Diweddaraf yn gyntaf
|
||||||
about.goodies.next_unread_bookmarklet=Botwm eitem nesaf heb ei ddarllen (llusgo i far nodau)
|
about.goodies.next_unread_bookmarklet=Botwm eitem nesaf heb ei ddarllen (llusgo i far nodau)
|
||||||
about.translation=Translation
|
about.translation=Cyfieithiad
|
||||||
about.translation.message=Rydym ni angen dy help i gyfieithu CommaFeed.
|
about.translation.message=Rydym angen dy help i gyfieithu CommaFeed.
|
||||||
about.translation.link=Gweler sut i gyfrannu i gyfieithiadau.
|
about.translation.link=Gweler sut i gyfrannu i gyfieithiadau.
|
||||||
about.announcements=Datganiadau
|
about.announcements=Datganiadau
|
||||||
about.rest_api.line1=Mae CommaFeed wedi cael ei adeiladu ar JAX-RS ac AngularJS. Mae REST API ar gael.
|
about.rest_api.line1=Adeiladir CommaFeed ar JAX-RS ac AngularJS. Mae REST API ar gael.
|
||||||
about.rest_api.link_to_documentation=Dolen i'r ddogfennaeth.
|
about.rest_api.link_to_documentation=Dolen i'r ddogfennaeth.
|
||||||
|
|
||||||
about.shortcuts.mouse_middleclick=llygoden clic-canol
|
about.shortcuts.mouse_middleclick=clic botwm canol llygoden
|
||||||
about.shortcuts.open_next_entry=agor eitem nesaf
|
about.shortcuts.open_next_entry=agor yr eitem nesaf
|
||||||
about.shortcuts.open_previous_entry=agor eitem flaenorol
|
about.shortcuts.open_previous_entry=agor yr eitem flaenorol
|
||||||
about.shortcuts.spacebar=space/shift+space ####### Needs translation
|
about.shortcuts.spacebar=space/shift+space
|
||||||
about.shortcuts.move_page_down_up=moves the page down/up ####### Needs translation
|
about.shortcuts.move_page_down_up=symud y tudalen i lawr/fyny
|
||||||
about.shortcuts.focus_next_entry=gosod ffocws ar eitem nesaf heb ei hagor
|
about.shortcuts.focus_next_entry=newid ffocws i'r eitem nesaf heb ei hagor
|
||||||
about.shortcuts.focus_previous_entry=gosod ffocws ar eitem flaenorol heb ei hagor
|
about.shortcuts.focus_previous_entry=newid ffocws i'r eitem flaenorol heb ei hagor
|
||||||
about.shortcuts.open_next_feed=agor ffrwd neu gategori nesaf
|
about.shortcuts.open_next_feed=agor y ffrwd neu gategori nesaf
|
||||||
about.shortcuts.open_previous_feed=agor ffrwd neu gategori blaenorol
|
about.shortcuts.open_previous_feed=agor y ffrwd neu gategori blaenorol
|
||||||
about.shortcuts.open_close_current_entry=agor/cau eitem gyfredol
|
about.shortcuts.open_close_current_entry=agor/cau yr eitem gyfredol
|
||||||
about.shortcuts.open_current_entry_in_new_window=agor eitem gyfredol mewn ffenestr newydd
|
about.shortcuts.open_current_entry_in_new_window=agor yr eitem gyfredol mewn ffenestr newydd
|
||||||
about.shortcuts.open_current_entry_in_new_window_background=agor eitem gyfredol mewn ffenestr newydd yn y cefndir
|
about.shortcuts.open_current_entry_in_new_window_background=agor yr eitem gyfredol mewn ffenestr newydd yn y cefndir
|
||||||
about.shortcuts.star_unstar=serennu/dadserennu eitem gyfredol
|
about.shortcuts.star_unstar=serennu/dadserennu'r eitem gyfredol
|
||||||
about.shortcuts.mark_current_entry=marcio eitem gyfredol fel darllenwyd/heb ddarllen
|
about.shortcuts.mark_current_entry=marcio'r eitem gyfredol fel wedi/heb ei ddarllen
|
||||||
about.shortcuts.mark_all_as_read=marcio popeth fel darllenwyd
|
about.shortcuts.mark_all_as_read=marcio popeth fel wedi ei ddarllen
|
||||||
about.shortcuts.open_in_new_tab_mark_as_read=agor eitem mewn tab newydd a marcio fel darllenwyd
|
about.shortcuts.open_in_new_tab_mark_as_read=agor yr eitem mewn tab newydd a'i farcio fel wedi ei ddarllen
|
||||||
about.shortcuts.fullscreen=toggle full screen mode ####### Needs translation
|
about.shortcuts.fullscreen=toglo'r golwg sgrin lawn
|
||||||
about.shortcuts.font_size=increase/decrease font size of the current entry ####### Needs translation
|
about.shortcuts.font_size=cynyddu/lleihau maint ffont yr eitem gyfredol
|
||||||
about.shortcuts.go_to_all=go to the All view ####### Needs translation
|
about.shortcuts.go_to_all=newid i olwg 'Popeth'
|
||||||
about.shortcuts.go_to_starred=go to the Starred view ####### Needs translation
|
about.shortcuts.go_to_starred=newid i olwg 'Serennwyd'
|
||||||
about.shortcuts.feed_search=llywio i danysgrifiad trwy rhoi ei enw mewn
|
about.shortcuts.feed_search=llywio i danysgrifiad gan roi ei enw mewn
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ subscribe.category=Kategória
|
|||||||
import.google_reader_prefix=Importujte si RSS zdroje s vášho
|
import.google_reader_prefix=Importujte si RSS zdroje s vášho
|
||||||
import.google_reader_suffix= účtu.
|
import.google_reader_suffix= účtu.
|
||||||
import.google_download=Alternatívne, môžte nahrať váš subscriptions.xml súbor
|
import.google_download=Alternatívne, môžte nahrať váš subscriptions.xml súbor
|
||||||
import.google_download_link=Download it from here.
|
import.google_download_link=Stiahnuť to môžete s lokácie.
|
||||||
import.xml_file=OPML súbor
|
import.xml_file=OPML súbor
|
||||||
|
|
||||||
new_category.name=Názov
|
new_category.name=Názov
|
||||||
@@ -31,7 +31,7 @@ toolbar.all=Všetky
|
|||||||
toolbar.previous_entry=Predchádzajúca položka
|
toolbar.previous_entry=Predchádzajúca položka
|
||||||
toolbar.next_entry=Nasledujúca položka
|
toolbar.next_entry=Nasledujúca položka
|
||||||
toolbar.refresh=Obnoviť
|
toolbar.refresh=Obnoviť
|
||||||
toolbar.refresh_all=Force refresh all my feeds ####### Needs translation
|
toolbar.refresh_all=Vynútené obnovenie všetkých položiek
|
||||||
toolbar.sort_by_asc_desc=Zoradiť podľa najnovšieho/najstaršieho
|
toolbar.sort_by_asc_desc=Zoradiť podľa najnovšieho/najstaršieho
|
||||||
toolbar.titles_only=Náhľad titulkov
|
toolbar.titles_only=Náhľad titulkov
|
||||||
toolbar.expanded_view=Rozšírený náhľad
|
toolbar.expanded_view=Rozšírený náhľad
|
||||||
@@ -52,8 +52,8 @@ view.error_while_loading_feed=Počas načítavania sa vyskytla chyba
|
|||||||
view.keep_unread=Ponechať ako neprečítané
|
view.keep_unread=Ponechať ako neprečítané
|
||||||
view.no_unread_items=nemá žiadne neprečítané položky.
|
view.no_unread_items=nemá žiadne neprečítané položky.
|
||||||
view.mark_up_to_here=Až potiaľto označiť položky ako prečítané
|
view.mark_up_to_here=Až potiaľto označiť položky ako prečítané
|
||||||
view.search_for=searching for: ####### Needs translation
|
view.search_for=Hľadaný výraz:
|
||||||
view.no_search_results=No match found for the requested keywords ####### Needs translation
|
view.no_search_results=Nenašla sa žiadna zhoda pre hľadaný výraz.
|
||||||
|
|
||||||
feedsearch.hint=Zadajte názov odoberania...
|
feedsearch.hint=Zadajte názov odoberania...
|
||||||
feedsearch.help=Použite klávesu enter pre výber a smerové klávesy pre navigáciu.
|
feedsearch.help=Použite klávesu enter pre výber a smerové klávesy pre navigáciu.
|
||||||
@@ -61,14 +61,14 @@ feedsearch.result_prefix=Vaše odoberania:
|
|||||||
|
|
||||||
settings.general=Všeobecné
|
settings.general=Všeobecné
|
||||||
settings.general.language=Jazyk
|
settings.general.language=Jazyk
|
||||||
settings.general.language.contribute=Pomôžte s prekladom
|
settings.general.language.contribute=Zapojte sa do prekladu
|
||||||
settings.general.show_unread=Zobraziť príspevky a kategórie bez neprečítaných položiek
|
settings.general.show_unread=Zobraziť príspevky a kategórie bez neprečítaných položiek
|
||||||
settings.general.social_buttons=Zobraziť možnosti zdieľania
|
settings.general.social_buttons=Zobraziť možnosti zdieľania
|
||||||
settings.general.scroll_marks=Scrollovanie v rozšírenom náhľade označí položky ako prečítané
|
settings.general.scroll_marks=Scrollovanie v rozšírenom náhľade označí položky ako prečítané
|
||||||
settings.appearance=Vzhľad
|
settings.appearance=Vzhľad
|
||||||
settings.theme=Motív
|
settings.theme=Motív
|
||||||
settings.submit_your_theme=Nahrať vlastný motív
|
settings.submit_your_theme=Nahrať vlastný motív vzhľadu
|
||||||
settings.custom_css=Vlastný motív (CSS)
|
settings.custom_css=Vlastný motív vzhľadu (CSS)
|
||||||
|
|
||||||
details.feed_details=Detaily odoberania
|
details.feed_details=Detaily odoberania
|
||||||
details.url=URL odkaz
|
details.url=URL odkaz
|
||||||
@@ -76,10 +76,10 @@ details.website=Web stránka
|
|||||||
details.name=Názov
|
details.name=Názov
|
||||||
details.category=Kategória
|
details.category=Kategória
|
||||||
details.position=Pozícia
|
details.position=Pozícia
|
||||||
details.last_refresh=Posledné obnovenie
|
details.last_refresh=Predchádzajúce obnovenie
|
||||||
details.message=Last refresh message ####### Needs translation
|
details.message=Predchádzajúca správa obnovenia
|
||||||
details.next_refresh=Nasledujúce obnovenie
|
details.next_refresh=Nasledujúce obnovenie
|
||||||
details.queued_for_refresh=Vo fronte na obnovu
|
details.queued_for_refresh=Vo fronte
|
||||||
details.feed_url=URL RSS zdroja
|
details.feed_url=URL RSS zdroja
|
||||||
details.generate_api_key_first=Vygenerujte si API kľúč vo vašom profile.
|
details.generate_api_key_first=Vygenerujte si API kľúč vo vašom profile.
|
||||||
details.unsubscribe=Zrušiť odoberanie.
|
details.unsubscribe=Zrušiť odoberanie.
|
||||||
@@ -95,13 +95,13 @@ profile.passwords_do_not_match=Heslá sa nezhodujú
|
|||||||
profile.api_key=API kľúč
|
profile.api_key=API kľúč
|
||||||
profile.api_key_not_generated=Nie je vygenerovaný
|
profile.api_key_not_generated=Nie je vygenerovaný
|
||||||
profile.generate_new_api_key=Vygenerovať nový API kľúč
|
profile.generate_new_api_key=Vygenerovať nový API kľúč
|
||||||
profile.generate_new_api_key_info=Zmena hesla vygeneruje nový API kľúč
|
profile.generate_new_api_key_info=Zmenou hesla vygenerujete nový API kľúč
|
||||||
profile.opml_export=exportovať do formátu OPML
|
profile.opml_export=exportovať do formátu OPML
|
||||||
profile.delete_account=Odstrániť účet
|
profile.delete_account=Odstrániť účet
|
||||||
|
|
||||||
about.rest_api=REST API
|
about.rest_api=REST API
|
||||||
about.keyboard_shortcuts=Klávesové skratky
|
about.keyboard_shortcuts=Klávesové skratky
|
||||||
about.version=CommaFeed version ####### Needs translation
|
about.version=CommaFeed verzia
|
||||||
about.line1_prefix=CommaFeed je open source projekt. Zdrojový kód je dostupný na
|
about.line1_prefix=CommaFeed je open source projekt. Zdrojový kód je dostupný na
|
||||||
about.line1_suffix=.
|
about.line1_suffix=.
|
||||||
about.line2_prefix=V prípade, že narazíte na problém, ohláste ho prosím na stránkach
|
about.line2_prefix=V prípade, že narazíte na problém, ohláste ho prosím na stránkach
|
||||||
@@ -114,13 +114,13 @@ about.goodies.subscribe_url=URL
|
|||||||
about.goodies.chrome_extension=Rozšírenie pre prehliadač Chrome
|
about.goodies.chrome_extension=Rozšírenie pre prehliadač Chrome
|
||||||
about.goodies.firefox_extension=Rozšírenie pre prehliadač Firefox
|
about.goodies.firefox_extension=Rozšírenie pre prehliadač Firefox
|
||||||
about.goodies.opera_extension=Rozšírenie pre prehliadač Opera
|
about.goodies.opera_extension=Rozšírenie pre prehliadač Opera
|
||||||
about.goodies.subscribe_bookmarklet=Bookmarklet(kliknite)
|
about.goodies.subscribe_bookmarklet=Bookmarklet
|
||||||
about.goodies.subscribe_bookmarklet_asc=Zoradiť podľa najstaršieho
|
about.goodies.subscribe_bookmarklet_asc=Zoradiť podľa najstaršieho
|
||||||
about.goodies.subscribe_bookmarklet_desc=Zoradiť podľa najnovšieho
|
about.goodies.subscribe_bookmarklet_desc=Zoradiť podľa najnovšieho
|
||||||
about.goodies.next_unread_bookmarklet=Záložka nasledujúcej neprečítanej položky(pretiahuť k záložkám)
|
about.goodies.next_unread_bookmarklet=Záložka nasledujúcej neprečítanej položky(pretiahuť k záložkám)
|
||||||
about.translation=Preklad
|
about.translation=Preklad
|
||||||
about.translation.message=Pomôžte s prekladom CommaFeed.
|
about.translation.message=Pomôžte s prekladom CommaFeed.
|
||||||
about.translation.link=Zistite ako môžte pomocť s prekladom.
|
about.translation.link=Zistite, ako sa možete zapojiť do prekladu CommaFeed.
|
||||||
about.announcements=Oznámenia
|
about.announcements=Oznámenia
|
||||||
about.rest_api.line1=CommaFeed je postavený na JAX-RS a AngularJS. Dostupná je REST API.
|
about.rest_api.line1=CommaFeed je postavený na JAX-RS a AngularJS. Dostupná je REST API.
|
||||||
about.rest_api.link_to_documentation=Prejsť na dokumentáciu.
|
about.rest_api.link_to_documentation=Prejsť na dokumentáciu.
|
||||||
@@ -128,22 +128,21 @@ about.rest_api.link_to_documentation=Prejsť na dokumentáciu.
|
|||||||
about.shortcuts.mouse_middleclick=klik prostredným tlačítkom
|
about.shortcuts.mouse_middleclick=klik prostredným tlačítkom
|
||||||
about.shortcuts.open_next_entry=zobraziť nasledujúcu položku
|
about.shortcuts.open_next_entry=zobraziť nasledujúcu položku
|
||||||
about.shortcuts.open_previous_entry=zobraziť predchádzajúcu položku
|
about.shortcuts.open_previous_entry=zobraziť predchádzajúcu položku
|
||||||
about.shortcuts.spacebar=space/shift+medzerník
|
about.shortcuts.spacebar=medzerník/shift+medzerník
|
||||||
about.shortcuts.move_page_down_up=pohyb smerom dole/hore
|
about.shortcuts.move_page_down_up=pohyb smerom dole/hore
|
||||||
about.shortcuts.focus_next_entry=presun na nasledujúcu položku bez jej zobrazenia
|
about.shortcuts.focus_next_entry=presun na nasledujúcu položku bez jej zobrazenia
|
||||||
about.shortcuts.focus_previous_entry=presun na predchádzajúcu položku bez jej zobrazenia
|
about.shortcuts.focus_previous_entry=presun na predchádzajúcu položku bez jej zobrazenia
|
||||||
about.shortcuts.open_next_feed=presun na nasledujúci RSS zdroj alebo kategóriu
|
about.shortcuts.open_next_feed=presun na nasledujúci RSS zdroj alebo kategóriu
|
||||||
about.shortcuts.open_previous_feed=presun na predchádzajúci RSS zdroj alebo kategóriu
|
about.shortcuts.open_previous_feed=presun na predchádzajúci RSS zdroj alebo kategóriu
|
||||||
about.shortcuts.open_close_current_entry=zobraziť/zavrieť vybranú položku
|
about.shortcuts.open_close_current_entry=zobraziť vybranú položku
|
||||||
about.shortcuts.open_current_entry_in_new_window=zobraziť vybranú položku v novom okne
|
about.shortcuts.open_current_entry_in_new_window=zobraziť vybranú položku v novom okne
|
||||||
about.shortcuts.open_current_entry_in_new_window_background=otvoriť vybranú položku na pozadí
|
about.shortcuts.open_current_entry_in_new_window_background=otvoriť vybranú položku na pozadí
|
||||||
about.shortcuts.star_unstar=označiť vybranú položku ako obľúbenú/neobľúbenú
|
about.shortcuts.star_unstar=označiť vybranú položku ako obľúbená
|
||||||
about.shortcuts.mark_current_entry=označiť vybranú položku ako prečítanú/neprečítanú
|
about.shortcuts.mark_current_entry=označiť vybranú položku ako prečítanú/neprečítanú
|
||||||
about.shortcuts.mark_all_as_read=označiť všetky položky ako prečítané!
|
about.shortcuts.mark_all_as_read=označiť všetky položky ako prečítané!
|
||||||
about.shortcuts.open_in_new_tab_mark_as_read=zobraziť položku na novej karte a označí ju ako prečítanú
|
about.shortcuts.open_in_new_tab_mark_as_read=zobraziť položku na novej karte a označí ju ako prečítanú
|
||||||
about.shortcuts.fullscreen=zapnúť/vypnúť zobrazenie na celú obrazovku
|
about.shortcuts.fullscreen=prepnutie zobrazenia na celú obrazovku
|
||||||
about.shortcuts.font_size=zväčšiť/zmenšiť veľkost písma pre vybranú položku
|
about.shortcuts.font_size=zmeniť veľkosť písma pre vybranú položku
|
||||||
about.shortcuts.go_to_all=go to the All view ####### Needs translation
|
about.shortcuts.go_to_all=zobraziť všetky položky
|
||||||
about.shortcuts.go_to_starred=go to the Starred view ####### Needs translation
|
about.shortcuts.go_to_starred=zobraziť obľúbené položiek
|
||||||
about.shortcuts.feed_search=prejsť k odoberanému RSS zdroju vložením jeho názvu
|
about.shortcuts.feed_search=presun na odoberaný RSS zdroj vložením jeho názvu
|
||||||
|
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ view.error_while_loading_feed=Fel under laddning av denna prenumeration
|
|||||||
view.keep_unread=Håll oläst
|
view.keep_unread=Håll oläst
|
||||||
view.no_unread_items=har inga olästa poster.
|
view.no_unread_items=har inga olästa poster.
|
||||||
view.mark_up_to_here=Markera som läst upp till denna post
|
view.mark_up_to_here=Markera som läst upp till denna post
|
||||||
view.search_for=searching for: ####### Needs translation
|
view.search_for=söker efter:
|
||||||
view.no_search_results=No match found for the requested keywords ####### Needs translation
|
view.no_search_results=Inga resultat för valda nyckelord
|
||||||
|
|
||||||
feedsearch.hint=Skriv in en prenumeration...
|
feedsearch.hint=Skriv in en prenumeration...
|
||||||
feedsearch.help=Använd retur-tangenten för att välja och piltangenterna för att navigera.
|
feedsearch.help=Använd retur-tangenten för att välja och piltangenterna för att navigera.
|
||||||
@@ -77,7 +77,7 @@ details.name=Namn
|
|||||||
details.category=Kategori
|
details.category=Kategori
|
||||||
details.position=Position
|
details.position=Position
|
||||||
details.last_refresh=Senaste uppdatering
|
details.last_refresh=Senaste uppdatering
|
||||||
details.message=Last refresh message ####### Needs translation
|
details.message=Senaste uppdateringsmeddelande
|
||||||
details.next_refresh=Nästa uppdatering
|
details.next_refresh=Nästa uppdatering
|
||||||
details.queued_for_refresh=I kö för uppdatering
|
details.queued_for_refresh=I kö för uppdatering
|
||||||
details.feed_url=Prenumerationens URL
|
details.feed_url=Prenumerationens URL
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ log4j.logger.com.commafeed=INFO, CONSOLE
|
|||||||
log4j.logger.org=WARN, CONSOLE
|
log4j.logger.org=WARN, CONSOLE
|
||||||
log4j.logger.ro=WARN, CONSOLE
|
log4j.logger.ro=WARN, CONSOLE
|
||||||
log4j.logger.com.wordnik=FATAL, CONSOLE
|
log4j.logger.com.wordnik=FATAL, CONSOLE
|
||||||
|
log4j.logger.com.codahale=WARN, CONSOLE
|
||||||
|
|
||||||
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
|
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
|
||||||
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
|
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
|
||||||
|
|||||||
@@ -665,15 +665,17 @@ module.controller('FeedListCtrl', [
|
|||||||
'$route',
|
'$route',
|
||||||
'$state',
|
'$state',
|
||||||
'$window',
|
'$window',
|
||||||
|
'$timeout',
|
||||||
'$location',
|
'$location',
|
||||||
'EntryService',
|
'EntryService',
|
||||||
'SettingsService',
|
'SettingsService',
|
||||||
'FeedService',
|
'FeedService',
|
||||||
'CategoryService',
|
'CategoryService',
|
||||||
'AnalyticsService',
|
'AnalyticsService',
|
||||||
function($scope, $stateParams, $http, $route, $state, $window, $location, EntryService, SettingsService, FeedService,
|
function($scope, $stateParams, $http, $route, $state, $window, $timeout, $location, EntryService, SettingsService, FeedService,
|
||||||
CategoryService, AnalyticsService) {
|
CategoryService, AnalyticsService) {
|
||||||
|
|
||||||
|
$window = angular.element($window);
|
||||||
AnalyticsService.track();
|
AnalyticsService.track();
|
||||||
|
|
||||||
$scope.keywords = $location.search().q;
|
$scope.keywords = $location.search().q;
|
||||||
@@ -727,9 +729,6 @@ module.controller('FeedListCtrl', [
|
|||||||
}
|
}
|
||||||
|
|
||||||
var callback = function(data) {
|
var callback = function(data) {
|
||||||
if (data.offset === 0) {
|
|
||||||
$scope.entries = [];
|
|
||||||
}
|
|
||||||
for ( var i = 0; i < data.entries.length; i++) {
|
for ( var i = 0; i < data.entries.length; i++) {
|
||||||
var entry = data.entries[i];
|
var entry = data.entries[i];
|
||||||
if (!_.some($scope.entries, {
|
if (!_.some($scope.entries, {
|
||||||
@@ -758,6 +757,79 @@ module.controller('FeedListCtrl', [
|
|||||||
}, callback);
|
}, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var watch_scrolling = true;
|
||||||
|
var watch_current = true;
|
||||||
|
|
||||||
|
$scope.$watch('current', function(newValue, oldValue) {
|
||||||
|
if (!watch_current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (newValue && newValue !== oldValue) {
|
||||||
|
var force = $scope.navigationMode == 'keyboard';
|
||||||
|
|
||||||
|
// timeout here to execute after dom update
|
||||||
|
$timeout(function() {
|
||||||
|
var docTop = $(window).scrollTop();
|
||||||
|
var docBottom = docTop + $(window).height();
|
||||||
|
|
||||||
|
var elem = $('#entry_' + newValue.id);
|
||||||
|
var elemTop = elem.offset().top;
|
||||||
|
var elemBottom = elemTop + elem.height();
|
||||||
|
|
||||||
|
if (!force && (elemTop > docTop) && (elemBottom < docBottom)) {
|
||||||
|
// element is entirely visible
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
var scrollTop = elemTop - $('#toolbar').outerHeight();
|
||||||
|
watch_scrolling = false;
|
||||||
|
$('html, body').animate({
|
||||||
|
scrollTop : scrollTop
|
||||||
|
}, 400, 'swing', function() {
|
||||||
|
watch_scrolling = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var scrollHandler = function() {
|
||||||
|
if (!watch_scrolling || _.size($scope.entries) === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.navigationMode = 'scroll';
|
||||||
|
if (SettingsService.settings.viewMode == 'expanded') {
|
||||||
|
var w = $(window);
|
||||||
|
var docTop = w.scrollTop();
|
||||||
|
|
||||||
|
var current = null;
|
||||||
|
for ( var i = 0; i < $scope.entries.length; i++) {
|
||||||
|
var entry = $scope.entries[i];
|
||||||
|
var e = $('#entry_' + entry.id);
|
||||||
|
if (e.offset().top + e.height() > docTop + $('#toolbar').outerHeight()) {
|
||||||
|
current = entry;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var previous = $scope.current;
|
||||||
|
$scope.current = current;
|
||||||
|
if (previous != current) {
|
||||||
|
if (SettingsService.settings.scrollMarks) {
|
||||||
|
$scope.mark($scope.current, true);
|
||||||
|
}
|
||||||
|
watch_current = false;
|
||||||
|
$scope.$apply();
|
||||||
|
watch_current = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var scrollListener = _.throttle(scrollHandler, 200);
|
||||||
|
$window.on('scroll', scrollListener);
|
||||||
|
$scope.$on('$destroy', function() {
|
||||||
|
return $window.off('scroll', scrollListener);
|
||||||
|
});
|
||||||
|
|
||||||
$scope.goToFeed = function(id) {
|
$scope.goToFeed = function(id) {
|
||||||
$state.transitionTo('feeds.view', {
|
$state.transitionTo('feeds.view', {
|
||||||
_type : 'feed',
|
_type : 'feed',
|
||||||
@@ -955,16 +1027,6 @@ module.controller('FeedListCtrl', [
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.onScroll = function(entry) {
|
|
||||||
$scope.navigationMode = 'scroll';
|
|
||||||
if (SettingsService.settings.viewMode == 'expanded') {
|
|
||||||
$scope.current = entry;
|
|
||||||
if (SettingsService.settings.scrollMarks) {
|
|
||||||
$scope.mark(entry, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Mousetrap.bind('j', function(e) {
|
Mousetrap.bind('j', function(e) {
|
||||||
$scope.$apply(function() {
|
$scope.$apply(function() {
|
||||||
$scope.navigationMode = 'keyboard';
|
$scope.navigationMode = 'keyboard';
|
||||||
@@ -1416,10 +1478,13 @@ module.controller('HelpController', ['$scope', 'CategoryService', 'AnalyticsServ
|
|||||||
}]);
|
}]);
|
||||||
|
|
||||||
module.controller('FooterController', ['$scope', function($scope) {
|
module.controller('FooterController', ['$scope', function($scope) {
|
||||||
|
|
||||||
var baseUrl = window.location.href.substring(0, window.location.href.lastIndexOf('#'));
|
var baseUrl = window.location.href.substring(0, window.location.href.lastIndexOf('#'));
|
||||||
var hostname = window.location.hostname;
|
var hostname = window.location.hostname;
|
||||||
$scope.subToMeUrl = baseUrl + 'rest/feed/subscribe?url={feed}';
|
$scope.subToMeUrl = baseUrl + 'rest/feed/subscribe?url={feed}';
|
||||||
$scope.subToMeName = hostname.indexOf('www.commafeed.com') !== -1 ? 'CommaFeed' : 'CommaFeed (' + hostname + ')';
|
$scope.subToMeName = hostname.indexOf('www.commafeed.com') !== -1 ? 'CommaFeed' : 'CommaFeed (' + hostname + ')';
|
||||||
|
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
|
module.controller('MetricsCtrl', ['$scope', 'AdminMetricsService', function($scope, AdminMetricsService) {
|
||||||
|
$scope.metrics = AdminMetricsService.get();
|
||||||
|
}]);
|
||||||
|
|||||||
@@ -59,96 +59,6 @@ module.directive('ngBlur', function() {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when the top of the element is not visible anymore
|
|
||||||
*/
|
|
||||||
module.directive('onScrollMiddle', function() {
|
|
||||||
return {
|
|
||||||
restrict : 'A',
|
|
||||||
link : function(scope, element, attrs) {
|
|
||||||
|
|
||||||
var w = $(window);
|
|
||||||
var e = $(element);
|
|
||||||
var d = $(document);
|
|
||||||
|
|
||||||
var offset = parseInt(attrs.onScrollMiddleOffset, 10);
|
|
||||||
|
|
||||||
var down = function() {
|
|
||||||
var docTop = w.scrollTop();
|
|
||||||
var elemTop = e.offset().top;
|
|
||||||
var threshold = docTop === 0 ? elemTop - 1 : docTop + offset;
|
|
||||||
return (elemTop > threshold) ? 'below' : 'above';
|
|
||||||
};
|
|
||||||
var up = function() {
|
|
||||||
var docTop = w.scrollTop();
|
|
||||||
var elemTop = e.offset().top;
|
|
||||||
var elemBottom = elemTop + e.height();
|
|
||||||
var threshold = docTop === 0 ? elemBottom - 1 : docTop + offset;
|
|
||||||
return (elemBottom > threshold) ? 'below' : 'above';
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!w.data.scrollInit) {
|
|
||||||
w.data.scrollPosition = d.scrollTop();
|
|
||||||
w.data.scrollDirection = 'down';
|
|
||||||
|
|
||||||
var onScroll = function(e) {
|
|
||||||
var scroll = d.scrollTop();
|
|
||||||
w.data.scrollDirection = (scroll - w.data.scrollPosition > 0) ? 'down' : 'up';
|
|
||||||
w.data.scrollPosition = scroll;
|
|
||||||
scope.$apply();
|
|
||||||
};
|
|
||||||
w.bind('scroll', _.throttle(onScroll, 500));
|
|
||||||
w.data.scrollInit = true;
|
|
||||||
}
|
|
||||||
scope.$watch(down, function downCallback(value, oldValue) {
|
|
||||||
if (value != oldValue && value == 'above')
|
|
||||||
scope.$eval(attrs.onScrollMiddle);
|
|
||||||
});
|
|
||||||
scope.$watch(up, function upCallback(value, oldValue) {
|
|
||||||
if (value != oldValue && value == 'below')
|
|
||||||
scope.$eval(attrs.onScrollMiddle);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scrolls to the element if the value is true and the attribute is not fully
|
|
||||||
* visible, unless the attribute scroll-to-force is true
|
|
||||||
*/
|
|
||||||
module.directive('scrollTo', ['$timeout', function($timeout) {
|
|
||||||
return {
|
|
||||||
restrict : 'A',
|
|
||||||
link : function(scope, element, attrs) {
|
|
||||||
scope.$watch(attrs.scrollTo, function(value) {
|
|
||||||
if (!value)
|
|
||||||
return;
|
|
||||||
var force = scope.$eval(attrs.scrollToForce);
|
|
||||||
|
|
||||||
// timeout here to execute after dom update
|
|
||||||
$timeout(function() {
|
|
||||||
var docTop = $(window).scrollTop();
|
|
||||||
var docBottom = docTop + $(window).height();
|
|
||||||
|
|
||||||
var elemTop = $(element).offset().top;
|
|
||||||
var elemBottom = elemTop + $(element).height();
|
|
||||||
|
|
||||||
if (!force && (elemTop > docTop) && (elemBottom < docBottom)) {
|
|
||||||
// element is entirely visible
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
var offset = parseInt(attrs.scrollToOffset, 10);
|
|
||||||
var scrollTop = $(element).offset().top + offset;
|
|
||||||
$('html, body').animate({
|
|
||||||
scrollTop : scrollTop
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}]);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prevent mousewheel scrolling from propagating to the parent when scrollbar
|
* Prevent mousewheel scrolling from propagating to the parent when scrollbar
|
||||||
* reaches top or bottom
|
* reaches top or bottom
|
||||||
@@ -409,27 +319,24 @@ module.directive('droppable', ['CategoryService', 'FeedService', function(Catego
|
|||||||
};
|
};
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
module.filter('highlight', function() {
|
module.directive('metricMeter', function() {
|
||||||
return function(html, keywords) {
|
return {
|
||||||
if (keywords) {
|
scope : {
|
||||||
var handleKeyword = function(token, html) {
|
metric : '=',
|
||||||
var expr = new RegExp(token, 'gi');
|
label : '='
|
||||||
var container = $('<span>').html(html);
|
},
|
||||||
var elements = container.find('*').addBack();
|
restrict : 'E',
|
||||||
var textNodes = elements.not('iframe').contents().not(elements);
|
templateUrl : 'templates/_metrics.meter.html'
|
||||||
textNodes.each(function() {
|
};
|
||||||
var replaced = this.nodeValue.replace(expr, '<span class="highlight-search">$&</span>');
|
});
|
||||||
$('<span>').html(replaced).insertBefore(this);
|
|
||||||
$(this).remove();
|
module.directive('metricGauge', function() {
|
||||||
});
|
return {
|
||||||
return container.html();
|
scope : {
|
||||||
};
|
metric : '=',
|
||||||
|
label : '='
|
||||||
var tokens = keywords.split(' ');
|
},
|
||||||
for ( var i = 0; i < tokens.length; i++) {
|
restrict : 'E',
|
||||||
html = handleKeyword(tokens[i], html);
|
templateUrl : 'templates/_metrics.gauge.html'
|
||||||
}
|
|
||||||
}
|
|
||||||
return html;
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -21,3 +21,28 @@ module.filter('entryDate', function() {
|
|||||||
module.filter('escape', function() {
|
module.filter('escape', function() {
|
||||||
return encodeURIComponent;
|
return encodeURIComponent;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
module.filter('highlight', function() {
|
||||||
|
return function(html, keywords) {
|
||||||
|
if (keywords) {
|
||||||
|
var handleKeyword = function(token, html) {
|
||||||
|
var expr = new RegExp(token, 'gi');
|
||||||
|
var container = $('<span>').html(html);
|
||||||
|
var elements = container.find('*').addBack();
|
||||||
|
var textNodes = elements.not('iframe').contents().not(elements);
|
||||||
|
textNodes.each(function() {
|
||||||
|
var replaced = this.nodeValue.replace(expr, '<span class="highlight-search">$&</span>');
|
||||||
|
$('<span>').html(replaced).insertBefore(this);
|
||||||
|
$(this).remove();
|
||||||
|
});
|
||||||
|
return container.html();
|
||||||
|
};
|
||||||
|
|
||||||
|
var tokens = keywords.split(' ');
|
||||||
|
for ( var i = 0; i < tokens.length; i++) {
|
||||||
|
html = handleKeyword(tokens[i], html);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return html;
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -100,6 +100,11 @@ app.config(['$routeProvider', '$stateProvider', '$urlRouterProvider', '$httpProv
|
|||||||
templateUrl : 'templates/admin.settings.html',
|
templateUrl : 'templates/admin.settings.html',
|
||||||
controller : 'ManageSettingsCtrl'
|
controller : 'ManageSettingsCtrl'
|
||||||
});
|
});
|
||||||
|
$stateProvider.state('admin.metrics', {
|
||||||
|
url : '/metrics',
|
||||||
|
templateUrl : 'templates/admin.metrics.html',
|
||||||
|
controller : 'MetricsCtrl'
|
||||||
|
});
|
||||||
|
|
||||||
$urlRouterProvider.when('/', '/feeds/view/category/all');
|
$urlRouterProvider.when('/', '/feeds/view/category/all');
|
||||||
$urlRouterProvider.when('/admin', '/admin/settings');
|
$urlRouterProvider.when('/admin', '/admin/settings');
|
||||||
|
|||||||
@@ -291,6 +291,12 @@ module.factory('AdminSettingsService', ['$resource', function($resource) {
|
|||||||
return res;
|
return res;
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
|
module.factory('AdminMetricsService', ['$resource', function($resource) {
|
||||||
|
var res = $resource('rest/admin/metrics/');
|
||||||
|
return res;
|
||||||
|
}]);
|
||||||
|
|
||||||
|
|
||||||
module.factory('AdminCleanupService', ['$resource', function($resource) {
|
module.factory('AdminCleanupService', ['$resource', function($resource) {
|
||||||
var actions = {
|
var actions = {
|
||||||
findDuplicateFeeds : {
|
findDuplicateFeeds : {
|
||||||
|
|||||||
@@ -6,6 +6,10 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-horizontal .control-group {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.bidi-embed {
|
.bidi-embed {
|
||||||
unicode-bidi: embed;
|
unicode-bidi: embed;
|
||||||
}
|
}
|
||||||
@@ -65,6 +69,10 @@
|
|||||||
height: 16px;
|
height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
blockquote p {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
.btn,.btn-large,.btn-small,.btn-mini {
|
.btn,.btn-large,.btn-small,.btn-mini {
|
||||||
border-top-left-radius: 2px;
|
border-top-left-radius: 2px;
|
||||||
border-top-right-radius: 2px;
|
border-top-right-radius: 2px;
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
}
|
}
|
||||||
body.left-menu-active .sidebar-nav-fixed {
|
body.left-menu-active .sidebar-nav-fixed {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
body.left-menu-active .main-content {
|
body.left-menu-active .main-content {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
|
|||||||
@@ -1,66 +1,110 @@
|
|||||||
#theme-svetla {
|
#theme-svetla {
|
||||||
/*background color*/
|
/*bg color*/
|
||||||
body, div.form-actions, .toolbar {
|
body, div.form-actions, .toolbar, .entrylist-header ng-scope {
|
||||||
background: #E3D4D1;
|
background: #AFB8BE;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.form-actions, div.page-header {
|
.form-actions, div.page-header {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre, #feed-accordion .unread .entry-heading {
|
pre, #feed-accordion .unread .entry-heading {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*feeds tree*/
|
/*feeds tree*/
|
||||||
.css-treeview li .tree-item:hover {
|
.css-treeview li .tree-item:hover {
|
||||||
background: grey;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/*feeds list*/
|
/*feeds list*/
|
||||||
|
|
||||||
#feed-accordion .entry-buttons { /*share panel*/
|
#feed-accordion .entry-buttons { /*share panel*/
|
||||||
background: transparent;
|
background: transparent;
|
||||||
/* uncomment if ---> border: 1px solid black; */
|
border:none;
|
||||||
}
|
/* ---> border: 1px solid black; */
|
||||||
|
}
|
||||||
|
|
||||||
#feed-accordion .entry {
|
#feed-accordion .entry {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#feed-accordion .unread .entry-heading:hover {
|
/* read feed bg */
|
||||||
background: grey;
|
#feed-accordion .entry-heading {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* readed feed background */
|
.dropdown-menu.pull-right li.divider {
|
||||||
#feed-accordion .entry-heading {
|
height: 0px;
|
||||||
background: #987B77;
|
background: transparent;
|
||||||
}
|
border-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
ul.dropdown-menu.pull-right li.divider {
|
.dropdown-menu {
|
||||||
height: 0px;
|
background: #E6E6E6;
|
||||||
background: transparent;
|
border-radius: 0px 0px 4px 4px;
|
||||||
border-bottom: 0px;
|
box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.dropdown-menu {
|
/**/
|
||||||
background: #E6E6E6;
|
.btn, .btn.dropdown-toggle {
|
||||||
}
|
background: #CFC7BE;
|
||||||
|
border: 1px solid #A7B5BE;
|
||||||
|
border-radius: 0px;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
/**/
|
.btn.dropdown-toggle {
|
||||||
.btn {
|
/*background: #eae7e3;*/
|
||||||
background: #B8ACA4;
|
}
|
||||||
border: 1px solid grey;/* ! */
|
|
||||||
}
|
|
||||||
|
|
||||||
button.btn.dropdown-toggle {
|
.btn.active {
|
||||||
background: #7B7672;
|
box-shadow: none;/*!*/
|
||||||
}
|
}
|
||||||
|
|
||||||
button.btn.dropdown-toggle:hover {
|
button.btn.dropdown-toggle:hover {
|
||||||
background: #e6e6e6;
|
background: #e6e6e6;
|
||||||
}
|
}
|
||||||
span.hidden-phone.hidden-tablet.ng-binding {
|
span.hidden-phone.hidden-tablet.ng-binding {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
.entrylist-header ng-scope {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entrylist-header {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-buttons.form-horizontal {
|
||||||
|
border-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************/
|
||||||
|
li.pointer a {
|
||||||
|
border-radius: 0px;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.pointer a:hover {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: #E6E6E6;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:focus {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
color: #323639;
|
||||||
|
text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.75);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#feed-accordion.expanded .entry {
|
||||||
|
box-shadow: none;
|
||||||
|
border-color: #CFC7BE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
4
src/main/webapp/templates/_metrics.gauge.html
Normal file
4
src/main/webapp/templates/_metrics.gauge.html
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<div>
|
||||||
|
<span>{{label}}</span>
|
||||||
|
<span>{{metric.value}}</span>
|
||||||
|
</div>
|
||||||
20
src/main/webapp/templates/_metrics.meter.html
Normal file
20
src/main/webapp/templates/_metrics.meter.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<div>
|
||||||
|
<span>{{label}}</span>
|
||||||
|
<dl class="dl-horizontal">
|
||||||
|
<dt>Mean</dt>
|
||||||
|
<dd>{{metric.meanRate | number:2}}</dd>
|
||||||
|
|
||||||
|
<dt>1 min</dt>
|
||||||
|
<dd>{{metric.oneMinuteRate | number:2}}</dd>
|
||||||
|
|
||||||
|
<dt>5 min</dt>
|
||||||
|
<dd>{{metric.fiveMinuteRate | number:2}}</dd>
|
||||||
|
|
||||||
|
<dt>15 min</dt>
|
||||||
|
<dd>{{metric.fifteenMinuteRate | number:2}}</dd>
|
||||||
|
|
||||||
|
<dt>Total</dt>
|
||||||
|
<dd>{{metric.count}}</dd>
|
||||||
|
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
13
src/main/webapp/templates/admin.metrics.html
Normal file
13
src/main/webapp/templates/admin.metrics.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<div>
|
||||||
|
<metric-meter metric="metrics.meters['com.commafeed.backend.feeds.FeedRefreshTaskGiver.refill']" label="'Refresh queue refill rate (/sec)'"></metric-meter>
|
||||||
|
<metric-meter metric="metrics.meters['com.commafeed.backend.feeds.FeedRefreshTaskGiver.feedRefreshed']" label="'Feed refreshed (/sec)'"></metric-meter>
|
||||||
|
<metric-meter metric="metrics.meters['com.commafeed.backend.feeds.FeedRefreshUpdater.feedUpdated']" label="'Feed updated (/sec)'"></metric-meter>
|
||||||
|
<metric-meter metric="metrics.meters['com.commafeed.backend.feeds.FeedRefreshUpdater.entryCacheHit']" label="'Entry cache hit (/sec)'"></metric-meter>
|
||||||
|
<metric-meter metric="metrics.meters['com.commafeed.backend.feeds.FeedRefreshUpdater.entryCacheMiss']" label="'Entry cache miss (/sec)'"></metric-meter>
|
||||||
|
|
||||||
|
<metric-gauge metric="metrics.gauges['com.commafeed.backend.feeds.FeedRefreshExecutor.feed-refresh-updater.active']" label="'Feed Updater active'"></metric-gauge>
|
||||||
|
<metric-gauge metric="metrics.gauges['com.commafeed.backend.feeds.FeedRefreshExecutor.feed-refresh-updater.pending']" label="'Feed Updater queued'"></metric-gauge>
|
||||||
|
|
||||||
|
<metric-gauge metric="metrics.gauges['com.commafeed.backend.feeds.FeedRefreshExecutor.feed-refresh-worker.active']" label="'Feed Worker active'"></metric-gauge>
|
||||||
|
<metric-gauge metric="metrics.gauges['com.commafeed.backend.feeds.FeedRefreshExecutor.feed-refresh-worker.pending']" label="'Feed Worker queued'"></metric-gauge>
|
||||||
|
</div>
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="span10 main-content">
|
<div class="span10 main-content">
|
||||||
<div class="toolbar" ng-include="'templates/_toolbar.html'"></div>
|
<div id="toolbar" class="toolbar" ng-include="'templates/_toolbar.html'"></div>
|
||||||
<div class="entryList">
|
<div class="entryList">
|
||||||
<div ui-view></div>
|
<div ui-view></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,8 +18,6 @@
|
|||||||
ng-class="{'expanded' : settingsService.settings.viewMode == 'expanded' }">
|
ng-class="{'expanded' : settingsService.settings.viewMode == 'expanded' }">
|
||||||
<div ng-show="message && errorCount > 10">${view.error_while_loading_feed} : {{message}}</div>
|
<div ng-show="message && errorCount > 10">${view.error_while_loading_feed} : {{message}}</div>
|
||||||
<div ng-repeat="entry in entries" class="entry entry-font-size-{{font_size}}" id="entry_{{entry.id}}"
|
<div ng-repeat="entry in entries" class="entry entry-font-size-{{font_size}}" id="entry_{{entry.id}}"
|
||||||
scroll-to="navigationMode != 'scroll' && current == entry" scroll-to-force="navigationMode == 'keyboard'" scroll-to-offset="-50"
|
|
||||||
on-scroll-middle="onScroll(entry)" on-scroll-middle-offset="50"
|
|
||||||
ng-class="{unread: entry.read == false, current: current==entry, open: isOpen, closed: !isOpen }">
|
ng-class="{unread: entry.read == false, current: current==entry, open: isOpen, closed: !isOpen }">
|
||||||
<div class="entry-heading">
|
<div class="entry-heading">
|
||||||
<a href="{{entry.url}}" target="_blank" class="entry-heading-link" ng-click="noop($event)" ng-mouseup="entryClicked(entry, $event)">
|
<a href="{{entry.url}}" target="_blank" class="entry-heading-link" ng-click="noop($event)" ng-mouseup="entryClicked(entry, $event)">
|
||||||
@@ -85,25 +83,28 @@
|
|||||||
</label>
|
</label>
|
||||||
|
|
||||||
<span class="share-buttons" ui-if="settingsService.settings.socialButtons">
|
<span class="share-buttons" ui-if="settingsService.settings.socialButtons">
|
||||||
<a href="mailto:?subject={{entry.title|escape}}&body={{entry.url|escape}}" popup>
|
<a href="mailto:?subject={{entry.title|escape}}&body={{entry.url|escape}}" title="E-mail" popup>
|
||||||
<i class="icon-envelope"></i>
|
<i class="icon-envelope"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="http://www.facebook.com/sharer.php?u=={{entry.url|escape}}" popup>
|
<a href="https://mail.google.com/mail/?view=cm&fs=1&tf=1&source=mailto&su={{entry.title|escape}}&body={{entry.url|escape}}" title="Gmail" popup>
|
||||||
|
<i class="icon-gmail"></i>
|
||||||
|
</a>
|
||||||
|
<a href="http://www.facebook.com/sharer.php?u=={{entry.url|escape}}" title="Facebook" popup>
|
||||||
<i class="icon-facebook"></i>
|
<i class="icon-facebook"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="http://twitter.com/share?text={{entry.title|escape}}&url={{entry.url|escape}}" popup>
|
<a href="http://twitter.com/share?text={{entry.title|escape}}&url={{entry.url|escape}}" title="Twitter" popup>
|
||||||
<i class="icon-twitter"></i>
|
<i class="icon-twitter"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://plus.google.com/share?url={{entry.url|escape}}" popup>
|
<a href="https://plus.google.com/share?url={{entry.url|escape}}" title="Google+" popup>
|
||||||
<i class="icon-google-plus"></i>
|
<i class="icon-google-plus"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://getpocket.com/save?url={{entry.url|escape}}&title={{entry.title|escape}}" popup>
|
<a href="https://getpocket.com/save?url={{entry.url|escape}}&title={{entry.title|escape}}" title="Pocket" popup>
|
||||||
<i class="icon-pocket"></i>
|
<i class="icon-pocket"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://www.instapaper.com/hello2?url={{entry.url|escape}}&title={{entry.title|escape}}" popup>
|
<a href="https://www.instapaper.com/hello2?url={{entry.url|escape}}&title={{entry.title|escape}}" title="Instapaper" popup>
|
||||||
<i class="icon-instapaper"></i>
|
<i class="icon-instapaper"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://bufferapp.com/add?url={{entry.url|escape}}&text={{entry.title|escape}}" popup>
|
<a href="https://bufferapp.com/add?url={{entry.url|escape}}&text={{entry.title|escape}}" title="Buffer" popup>
|
||||||
<i class="icon-buffer"></i>
|
<i class="icon-buffer"></i>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
6
src/main/webapp/vendor/zocial/zocial.css
vendored
6
src/main/webapp/vendor/zocial/zocial.css
vendored
@@ -39,3 +39,9 @@
|
|||||||
font-family: 'zocial';
|
font-family: 'zocial';
|
||||||
content: "\00E5";
|
content: "\00E5";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-gmail:before {
|
||||||
|
font-style: normal;
|
||||||
|
font-family: 'zocial';
|
||||||
|
content: "m";
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user