forked from Archives/Athou_commafeed
Compare commits
255 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbcd79e49f | ||
|
|
4dabf47822 | ||
|
|
db258d4ecc | ||
|
|
8b237db690 | ||
|
|
416350c004 | ||
|
|
8d63377e78 | ||
|
|
377176df05 | ||
|
|
95da0078b3 | ||
|
|
6392b87afc | ||
|
|
ba04d2adfe | ||
|
|
517ce1a726 | ||
|
|
36492cbff5 | ||
|
|
4b46aa08ac | ||
|
|
1a9a80c0da | ||
|
|
32a30019a7 | ||
|
|
bb72131354 | ||
|
|
3a8d72cab4 | ||
|
|
f5f7a8e63b | ||
|
|
570c4f3a1f | ||
|
|
172164b74b | ||
|
|
49835ae234 | ||
|
|
c4f1e910f8 | ||
|
|
3a621b61c6 | ||
|
|
c28f0d6788 | ||
|
|
2db9224ffc | ||
|
|
043b1df585 | ||
|
|
0626200787 | ||
|
|
b7ee61a8df | ||
|
|
6e1cdaf50e | ||
|
|
e770f802e7 | ||
|
|
8e4cf77fcb | ||
|
|
bc3bd42ce3 | ||
|
|
f73e0ba307 | ||
|
|
5703b5e8d4 | ||
|
|
cecbb2cf72 | ||
|
|
8638e4751d | ||
|
|
3b69e3b029 | ||
|
|
dced21c8e4 | ||
|
|
dab26af294 | ||
|
|
65f118e561 | ||
|
|
67f533b9f6 | ||
|
|
93573bcdb7 | ||
|
|
2263801c55 | ||
|
|
10c34d0440 | ||
|
|
4430ef3847 | ||
|
|
8e331b908d | ||
|
|
dbc6fb58e0 | ||
|
|
db298ab684 | ||
|
|
170a6095e6 | ||
|
|
6dd1bf3281 | ||
|
|
b1500cebfd | ||
|
|
6202bdbc28 | ||
|
|
39bfb61b95 | ||
|
|
fa79524ed4 | ||
|
|
ab5b70e52b | ||
|
|
4f8cd53b83 | ||
|
|
afb6221e5e | ||
|
|
f78aedc30d | ||
|
|
80ff2c8ff7 | ||
|
|
579a77dfc9 | ||
|
|
f902d967a6 | ||
|
|
0899e0b0bf | ||
|
|
65d6f8616b | ||
|
|
5c27f0834c | ||
|
|
a5f7b56bf2 | ||
|
|
63ec92038c | ||
|
|
464ac36ddb | ||
|
|
840bc2ef7a | ||
|
|
e248504528 | ||
|
|
f4f3d9ca48 | ||
|
|
e727ee414b | ||
|
|
1e9295b386 | ||
|
|
b980cdc2c2 | ||
|
|
fbe722facd | ||
|
|
1897d8e0c0 | ||
|
|
3745a152aa | ||
|
|
a7731acb08 | ||
|
|
16dd5deed4 | ||
|
|
c9f70650a0 | ||
|
|
eaa84253df | ||
|
|
45abcd7385 | ||
|
|
8a633aa648 | ||
|
|
05e092062d | ||
|
|
e83602a05c | ||
|
|
abf8666e24 | ||
|
|
af1ccc6669 | ||
|
|
cdcbfbff68 | ||
|
|
6860940afc | ||
|
|
bfc2ee3663 | ||
|
|
b104622081 | ||
|
|
a861387bd7 | ||
|
|
b0f2260fad | ||
|
|
97f0d98ffd | ||
|
|
1ad58a029c | ||
|
|
4c27da0433 | ||
|
|
faf69b43c3 | ||
|
|
7fff561268 | ||
|
|
5e1360a65b | ||
|
|
cc92d2f546 | ||
|
|
def75a250f | ||
|
|
15cd7caf9b | ||
|
|
41a51530ef | ||
|
|
3a101941b3 | ||
|
|
0976fee4df | ||
|
|
f87da777da | ||
|
|
e1c2bf0890 | ||
|
|
b829defb30 | ||
|
|
fa8770d2a7 | ||
|
|
222c8a65af | ||
|
|
76f5b67ac4 | ||
|
|
1791d49efe | ||
|
|
64e1b5df09 | ||
|
|
e1ff077623 | ||
|
|
1361072558 | ||
|
|
5119434d21 | ||
|
|
b29540b14e | ||
|
|
e69785bb89 | ||
|
|
76465fee07 | ||
|
|
b52c459ebb | ||
|
|
1d73982545 | ||
|
|
74f6c45f36 | ||
|
|
0490b528e4 | ||
|
|
ffa1e14449 | ||
|
|
b8fe89b2f4 | ||
|
|
94b293202c | ||
|
|
7ef143a642 | ||
|
|
057f6916e9 | ||
|
|
e24e892cb3 | ||
|
|
78976b06e2 | ||
|
|
96cfcd5b2b | ||
|
|
12bda0122c | ||
|
|
4ac4e5abf2 | ||
|
|
268f0f53a8 | ||
|
|
71521f3428 | ||
|
|
6101fb2bef | ||
|
|
8f6aa0896b | ||
|
|
b8f0af5b2e | ||
|
|
32730f6c41 | ||
|
|
7caa99f8f2 | ||
|
|
4f8e2ab478 | ||
|
|
5c44f392ca | ||
|
|
174d21fd4e | ||
|
|
c2ed6d47f1 | ||
|
|
0f6f717d09 | ||
|
|
d7fb637f68 | ||
|
|
fce9086b27 | ||
|
|
97586cd2c8 | ||
|
|
b74458f0b0 | ||
|
|
7c7a0fceaf | ||
|
|
425a8880cd | ||
|
|
23fe90ec64 | ||
|
|
c01ec5d039 | ||
|
|
4f284165c2 | ||
|
|
2a62ccff11 | ||
|
|
d09cf472dd | ||
|
|
5c721ae6f5 | ||
|
|
2bb8fcdb5f | ||
|
|
6eda93098b | ||
|
|
6344f554d6 | ||
|
|
7e4c1f374c | ||
|
|
28eaab7f7d | ||
|
|
1937944f7e | ||
|
|
3b4b84fdab | ||
|
|
32325bb49c | ||
|
|
c01c1e93f9 | ||
|
|
eac096019f | ||
|
|
9f9389e846 | ||
|
|
a71317881f | ||
|
|
7092824c96 | ||
|
|
0ff998bbd7 | ||
|
|
fc318ad211 | ||
|
|
73323335cb | ||
|
|
ef57c5523d | ||
|
|
846f4a7222 | ||
|
|
05036778d6 | ||
|
|
52df661238 | ||
|
|
7957dc237e | ||
|
|
3fe419ba2f | ||
|
|
61944656b8 | ||
|
|
1cb997b66d | ||
|
|
89463808db | ||
|
|
6aca66d8cf | ||
|
|
38f8102fb3 | ||
|
|
e709499240 | ||
|
|
0b714d5e52 | ||
|
|
98e4f0c6dc | ||
|
|
d82d0af565 | ||
|
|
d8abb7039d | ||
|
|
84dc11048d | ||
|
|
bad915bbaa | ||
|
|
287dea2d36 | ||
|
|
a0b937769d | ||
|
|
6acef4a406 | ||
|
|
8b77eb9850 | ||
|
|
6f22836dcb | ||
|
|
a4347c8878 | ||
|
|
836f7eff09 | ||
|
|
c993bd472d | ||
|
|
431ab92a02 | ||
|
|
94f469a6b1 | ||
|
|
3fec1c6890 | ||
|
|
f8316911bd | ||
|
|
642d1f6be5 | ||
|
|
5a82c3a130 | ||
|
|
6a8174afac | ||
|
|
f4c86634f7 | ||
|
|
322e588a4e | ||
|
|
822dee7a13 | ||
|
|
101e179788 | ||
|
|
57abee6cf0 | ||
|
|
b615847b09 | ||
|
|
ffef87e249 | ||
|
|
ba3b8df4c9 | ||
|
|
40175d3e54 | ||
|
|
06b047cfe6 | ||
|
|
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 |
@@ -261,7 +261,7 @@
|
|||||||
<property name="external_port">${env.OPENSHIFT_JBOSSEAP_CLUSTER_PROXY_PORT}
|
<property name="external_port">${env.OPENSHIFT_JBOSSEAP_CLUSTER_PROXY_PORT}
|
||||||
</property>
|
</property>
|
||||||
<property name="bind_port">7600</property>
|
<property name="bind_port">7600</property>
|
||||||
<property name="bind_addr">${env.OPENSHIFT_INTERNAL_IP}</property>
|
<property name="bind_addr">${env.OPENSHIFT_JBOSSEAP_IP}</property>
|
||||||
</transport>
|
</transport>
|
||||||
<protocol type="TCPPING">
|
<protocol type="TCPPING">
|
||||||
<property name="timeout">3000</property>
|
<property name="timeout">3000</property>
|
||||||
@@ -476,15 +476,15 @@
|
|||||||
|
|
||||||
<interfaces>
|
<interfaces>
|
||||||
<interface name="management">
|
<interface name="management">
|
||||||
<loopback-address value="${env.OPENSHIFT_INTERNAL_IP}" />
|
<loopback-address value="${env.OPENSHIFT_JBOSSEAP_IP}" />
|
||||||
</interface>
|
</interface>
|
||||||
<interface name="public">
|
<interface name="public">
|
||||||
<loopback-address value="${env.OPENSHIFT_INTERNAL_IP}" />
|
<loopback-address value="${env.OPENSHIFT_JBOSSEAP_IP}" />
|
||||||
</interface>
|
</interface>
|
||||||
<interface name="unsecure">
|
<interface name="unsecure">
|
||||||
<!-- Used for IIOP sockets in the standarad configuration. To secure JacORB
|
<!-- Used for IIOP sockets in the standarad configuration. To secure JacORB
|
||||||
you need to setup SSL -->
|
you need to setup SSL -->
|
||||||
<loopback-address value="${env.OPENSHIFT_INTERNAL_IP}" />
|
<loopback-address value="${env.OPENSHIFT_JBOSSEAP_IP}" />
|
||||||
</interface>
|
</interface>
|
||||||
</interfaces>
|
</interfaces>
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,7 @@
|
|||||||
rm -rf $OPENSHIFT_JBOSSAS_LOG_DIR\*.log.*
|
if [ $OPENSHIFT_JBOSSAS_LOG_DIR ]; then
|
||||||
|
rm -rf $OPENSHIFT_JBOSSAS_LOG_DIR/*.log.*
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $OPENSHIFT_JBOSSEAP_LOG_DIR ]; then
|
||||||
|
rm -rf $OPENSHIFT_JBOSSEAP_LOG_DIR/*.log.*
|
||||||
|
fi
|
||||||
15
README.md
15
README.md
@@ -41,7 +41,7 @@ To install maven and openjdk on Ubuntu, issue the following commands
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install openjdk-7-jdk maven3
|
sudo apt-get install openjdk-7-jdk maven3
|
||||||
|
|
||||||
Not required but if you don't, use 'mvn3' instead of 'mvn' for the rest of the instructions.
|
# Not required but if you don't, use 'mvn3' instead of 'mvn' for the rest of the instructions.
|
||||||
sudo ln -s /usr/bin/mvn3 /usr/bin/mvn
|
sudo ln -s /usr/bin/mvn3 /usr/bin/mvn
|
||||||
|
|
||||||
On Windows and other operating systems, just download maven 3.x from the [official site](http://maven.apache.org/), extract it somewhere and add the `bin` directory to your `PATH` environment variable.
|
On Windows and other operating systems, just download maven 3.x from the [official site](http://maven.apache.org/), extract it somewhere and add the `bin` directory to your `PATH` environment variable.
|
||||||
@@ -54,16 +54,16 @@ If you don't have git you can download the sources as a zip file from [here](htt
|
|||||||
|
|
||||||
Now build the application
|
Now build the application
|
||||||
|
|
||||||
Embedded HSQL database:
|
# Embedded HSQL database:
|
||||||
mvn clean package tomee:build -Pprod
|
mvn clean package tomee:build -Pprod
|
||||||
|
|
||||||
External MySQL database:
|
# External MySQL database:
|
||||||
mvn clean package tomee:build -Pprod -Pmysql
|
mvn clean package tomee:build -Pprod -Pmysql
|
||||||
|
|
||||||
External PostgreSQL database:
|
# External PostgreSQL database:
|
||||||
mvn clean package tomee:build -Pprod -Ppgsql
|
mvn clean package tomee:build -Pprod -Ppgsql
|
||||||
|
|
||||||
External Microsoft SQL Server database:
|
# External Microsoft SQL Server database:
|
||||||
mvn clean package tomee:build -Pprod -Pmssql
|
mvn clean package tomee:build -Pprod -Pmssql
|
||||||
|
|
||||||
It will generate a zip file at `target/commafeed.zip` with everything you need to run the application.
|
It will generate a zip file at `target/commafeed.zip` with everything you need to run the application.
|
||||||
@@ -74,12 +74,15 @@ It will generate a zip file at `target/commafeed.zip` with everything you need t
|
|||||||
* If you don't use the embedded database, create a database in your external database instance, then uncomment the `Resource` element corresponding to the database engine you use from `conf/tomee.xml` and edit the default credentials.
|
* If you don't use the embedded database, create a database in your external database instance, then uncomment the `Resource` element corresponding to the database engine you use from `conf/tomee.xml` and edit the default credentials.
|
||||||
* 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 nginx 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
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
set JAVA_OPTS=-Djava.net.preferIPv4Stack=true -Xmx1024m -XX:MaxPermSize=256m -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC
|
set JAVA_OPTS=-Djava.net.preferIPv4Stack=true -Xmx1024m -XX:MaxPermSize=256m -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -Djsse.enableSNIExtension=false
|
||||||
@@ -1 +1 @@
|
|||||||
export JAVA_OPTS="-Djava.net.preferIPv4Stack=true -Xmx1024m -XX:MaxPermSize=256m -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC"
|
export JAVA_OPTS="-Djava.net.preferIPv4Stack=true -Xmx1024m -XX:MaxPermSize=256m -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -Djsse.enableSNIExtension=false"
|
||||||
137
pom.xml
137
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.5.0-SNAPSHOT</version>
|
||||||
<packaging>war</packaging>
|
<packaging>war</packaging>
|
||||||
<name>CommaFeed</name>
|
<name>CommaFeed</name>
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
<artifactId>tomee-maven-plugin</artifactId>
|
<artifactId>tomee-maven-plugin</artifactId>
|
||||||
<version>1.5.2</version>
|
<version>1.5.2</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<tomeeVersion>1.5.2</tomeeVersion>
|
<tomeeVersion>1.6.0</tomeeVersion>
|
||||||
<tomeeClassifier>plus</tomeeClassifier>
|
<tomeeClassifier>plus</tomeeClassifier>
|
||||||
<tomeeHttpPort>8082</tomeeHttpPort>
|
<tomeeHttpPort>8082</tomeeHttpPort>
|
||||||
<args>-Xmx1024m -XX:MaxPermSize=512m -XX:+CMSClassUnloadingEnabled</args>
|
<args>-Xmx1024m -XX:MaxPermSize=512m -XX:+CMSClassUnloadingEnabled</args>
|
||||||
@@ -104,13 +104,18 @@
|
|||||||
<lib>org.hibernate.common:hibernate-commons-annotations:4.0.1.Final</lib>
|
<lib>org.hibernate.common:hibernate-commons-annotations:4.0.1.Final</lib>
|
||||||
<lib>org.hibernate:hibernate-validator:4.3.1.Final</lib>
|
<lib>org.hibernate:hibernate-validator:4.3.1.Final</lib>
|
||||||
<lib>org.jboss.logging:jboss-logging:3.1.3.GA</lib>
|
<lib>org.jboss.logging:jboss-logging:3.1.3.GA</lib>
|
||||||
|
<lib>org.javassist:javassist:3.15.0-GA</lib>
|
||||||
|
|
||||||
|
<lib>org.apache.openejb:openejb-bonecp:4.6.0</lib>
|
||||||
|
<lib>com.jolbox:bonecp:0.8.0.RELEASE</lib>
|
||||||
|
<lib>com.google.guava:guava:14.0.1</lib>
|
||||||
|
|
||||||
<lib>dom4j:dom4j:1.6.1</lib>
|
<lib>dom4j:dom4j:1.6.1</lib>
|
||||||
<lib>antlr:antlr:2.7.7</lib>
|
<lib>antlr:antlr:2.7.7</lib>
|
||||||
<lib>remove:openjpa-</lib>
|
<lib>remove:openjpa-</lib>
|
||||||
<lib>remove:hsqldb</lib>
|
<lib>remove:hsqldb</lib>
|
||||||
<lib>org.hsqldb:hsqldb:2.3.0</lib>
|
<lib>org.hsqldb:hsqldb:2.3.0</lib>
|
||||||
<lib>mysql:mysql-connector-java:5.1.24</lib>
|
<lib>mysql:mysql-connector-java:5.1.26</lib>
|
||||||
<lib>postgresql:postgresql:9.1-901.jdbc4</lib>
|
<lib>postgresql:postgresql:9.1-901.jdbc4</lib>
|
||||||
<lib>net.sourceforge.jtds:jtds:1.3.1</lib>
|
<lib>net.sourceforge.jtds:jtds:1.3.1</lib>
|
||||||
|
|
||||||
@@ -169,7 +174,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>pl.project13.maven</groupId>
|
<groupId>pl.project13.maven</groupId>
|
||||||
<artifactId>git-commit-id-plugin</artifactId>
|
<artifactId>git-commit-id-plugin</artifactId>
|
||||||
<version>2.1.5</version>
|
<version>2.1.7</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<goals>
|
<goals>
|
||||||
@@ -189,7 +194,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
<version>0.12.0</version>
|
<version>1.12.6</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -214,28 +219,28 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>redis.clients</groupId>
|
<groupId>redis.clients</groupId>
|
||||||
<artifactId>jedis</artifactId>
|
<artifactId>jedis</artifactId>
|
||||||
<version>2.1.0</version>
|
<version>2.2.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.liquibase</groupId>
|
<groupId>org.liquibase</groupId>
|
||||||
<artifactId>liquibase-core</artifactId>
|
<artifactId>liquibase-core</artifactId>
|
||||||
<version>3.0.2</version>
|
<version>3.1.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.guava</groupId>
|
<groupId>com.google.guava</groupId>
|
||||||
<artifactId>guava</artifactId>
|
<artifactId>guava</artifactId>
|
||||||
<version>14.0.1</version>
|
<version>16.0.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-beanutils</groupId>
|
<groupId>commons-beanutils</groupId>
|
||||||
<artifactId>commons-beanutils</artifactId>
|
<artifactId>commons-beanutils</artifactId>
|
||||||
<version>1.8.3</version>
|
<version>1.9.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-codec</groupId>
|
<groupId>commons-codec</groupId>
|
||||||
<artifactId>commons-codec</artifactId>
|
<artifactId>commons-codec</artifactId>
|
||||||
<version>1.8</version>
|
<version>1.9</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-collections</groupId>
|
<groupId>commons-collections</groupId>
|
||||||
@@ -260,7 +265,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-fileupload</groupId>
|
<groupId>commons-fileupload</groupId>
|
||||||
<artifactId>commons-fileupload</artifactId>
|
<artifactId>commons-fileupload</artifactId>
|
||||||
<version>1.3</version>
|
<version>1.3.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -298,34 +303,34 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.gwt</groupId>
|
<groupId>com.google.gwt</groupId>
|
||||||
<artifactId>gwt-servlet</artifactId>
|
<artifactId>gwt-servlet</artifactId>
|
||||||
<version>2.5.1</version>
|
<version>2.6.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.sourceforge.cssparser</groupId>
|
<groupId>net.sourceforge.cssparser</groupId>
|
||||||
<artifactId>cssparser</artifactId>
|
<artifactId>cssparser</artifactId>
|
||||||
<version>0.9.9</version>
|
<version>0.9.13</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.httpcomponents</groupId>
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
<artifactId>httpclient</artifactId>
|
<artifactId>httpclient</artifactId>
|
||||||
<version>4.2.5</version>
|
<version>4.3.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jsoup</groupId>
|
<groupId>org.jsoup</groupId>
|
||||||
<artifactId>jsoup</artifactId>
|
<artifactId>jsoup</artifactId>
|
||||||
<version>1.7.2</version>
|
<version>1.7.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
<artifactId>jackson-databind</artifactId>
|
<artifactId>jackson-databind</artifactId>
|
||||||
<version>2.2.2</version>
|
<version>2.3.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
<artifactId>slf4j-log4j12</artifactId>
|
<artifactId>slf4j-log4j12</artifactId>
|
||||||
<version>1.7.5</version>
|
<version>1.7.7</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>log4j</groupId>
|
<groupId>log4j</groupId>
|
||||||
@@ -336,28 +341,28 @@
|
|||||||
<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.14.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.14.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.14.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.14.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ro.isdc.wro4j</groupId>
|
<groupId>ro.isdc.wro4j</groupId>
|
||||||
<artifactId>wro4j-extensions</artifactId>
|
<artifactId>wro4j-extensions</artifactId>
|
||||||
<version>1.6.3</version>
|
<version>1.7.5</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -372,6 +377,88 @@
|
|||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.codahale.metrics</groupId>
|
||||||
|
<artifactId>metrics-core</artifactId>
|
||||||
|
<version>3.0.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.codahale.metrics</groupId>
|
||||||
|
<artifactId>metrics-json</artifactId>
|
||||||
|
<version>3.0.2</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.webjars</groupId>
|
||||||
|
<artifactId>lodash</artifactId>
|
||||||
|
<version>2.4.1-3</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.webjars</groupId>
|
||||||
|
<artifactId>jquery</artifactId>
|
||||||
|
<version>1.11.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.webjars</groupId>
|
||||||
|
<artifactId>bootstrap</artifactId>
|
||||||
|
<version>3.1.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.webjars</groupId>
|
||||||
|
<artifactId>jquery-mousewheel</artifactId>
|
||||||
|
<version>3.1.9</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.webjars</groupId>
|
||||||
|
<artifactId>angularjs</artifactId>
|
||||||
|
<version>1.2.16</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.webjars</groupId>
|
||||||
|
<artifactId>angular-ui-router</artifactId>
|
||||||
|
<version>0.2.8-2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.webjars</groupId>
|
||||||
|
<artifactId>angular-ui-utils</artifactId>
|
||||||
|
<version>0.1.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.webjars</groupId>
|
||||||
|
<artifactId>ui-select2</artifactId>
|
||||||
|
<version>0.0.5</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.webjars</groupId>
|
||||||
|
<artifactId>angular-ui-bootstrap</artifactId>
|
||||||
|
<version>0.2.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.webjars</groupId>
|
||||||
|
<artifactId>mousetrap</artifactId>
|
||||||
|
<version>1.4.6</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.webjars</groupId>
|
||||||
|
<artifactId>momentjs</artifactId>
|
||||||
|
<version>2.6.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.webjars</groupId>
|
||||||
|
<artifactId>ng-grid</artifactId>
|
||||||
|
<version>2.0.7</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.webjars</groupId>
|
||||||
|
<artifactId>device.js</artifactId>
|
||||||
|
<version>139f208</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.webjars</groupId>
|
||||||
|
<artifactId>ngInfiniteScroll</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
@@ -391,7 +478,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>ro.isdc.wro4j</groupId>
|
<groupId>ro.isdc.wro4j</groupId>
|
||||||
<artifactId>wro4j-maven-plugin</artifactId>
|
<artifactId>wro4j-maven-plugin</artifactId>
|
||||||
<version>1.6.3</version>
|
<version>1.7.5</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>js</id>
|
<id>js</id>
|
||||||
@@ -401,7 +488,7 @@
|
|||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<targetGroups>app</targetGroups>
|
<targetGroups>app</targetGroups>
|
||||||
<options>indent,devel,noarg,quotmark,laxcomma,laxbreak</options>
|
<options>devel,noarg,quotmark,laxcomma,laxbreak</options>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
<execution>
|
<execution>
|
||||||
@@ -538,7 +625,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>ro.isdc.wro4j</groupId>
|
<groupId>ro.isdc.wro4j</groupId>
|
||||||
<artifactId>wro4j-maven-plugin</artifactId>
|
<artifactId>wro4j-maven-plugin</artifactId>
|
||||||
<version>1.6.3</version>
|
<version>1.7.5</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<goals>
|
<goals>
|
||||||
|
|||||||
@@ -4,42 +4,45 @@ import java.io.IOException;
|
|||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import javax.net.ssl.KeyManager;
|
import javax.net.ssl.KeyManager;
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
import javax.net.ssl.SSLException;
|
|
||||||
import javax.net.ssl.SSLSession;
|
|
||||||
import javax.net.ssl.SSLSocket;
|
|
||||||
import javax.net.ssl.TrustManager;
|
import javax.net.ssl.TrustManager;
|
||||||
import javax.net.ssl.X509TrustManager;
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.apache.http.Consts;
|
||||||
import org.apache.http.Header;
|
import org.apache.http.Header;
|
||||||
|
import org.apache.http.HeaderElement;
|
||||||
import org.apache.http.HttpEntity;
|
import org.apache.http.HttpEntity;
|
||||||
|
import org.apache.http.HttpException;
|
||||||
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.HttpResponseInterceptor;
|
||||||
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.HttpResponseException;
|
import org.apache.http.client.HttpResponseException;
|
||||||
|
import org.apache.http.client.config.CookieSpecs;
|
||||||
|
import org.apache.http.client.config.RequestConfig;
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
import org.apache.http.client.methods.HttpGet;
|
import org.apache.http.client.methods.HttpGet;
|
||||||
import org.apache.http.client.params.CookiePolicy;
|
import org.apache.http.client.methods.HttpUriRequest;
|
||||||
import org.apache.http.client.params.HttpClientParams;
|
import org.apache.http.client.protocol.HttpClientContext;
|
||||||
import org.apache.http.conn.ClientConnectionManager;
|
import org.apache.http.config.ConnectionConfig;
|
||||||
import org.apache.http.conn.scheme.Scheme;
|
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||||
import org.apache.http.conn.scheme.SchemeRegistry;
|
import org.apache.http.entity.HttpEntityWrapper;
|
||||||
import org.apache.http.conn.ssl.SSLSocketFactory;
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
import org.apache.http.conn.ssl.X509HostnameVerifier;
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
import org.apache.http.impl.client.DecompressingHttpClient;
|
import org.apache.http.impl.client.HttpClients;
|
||||||
import org.apache.http.impl.client.DefaultHttpClient;
|
import org.apache.http.protocol.HttpContext;
|
||||||
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
|
|
||||||
import org.apache.http.impl.client.SystemDefaultHttpClient;
|
|
||||||
import org.apache.http.params.HttpConnectionParams;
|
|
||||||
import org.apache.http.params.HttpParams;
|
|
||||||
import org.apache.http.params.HttpProtocolParams;
|
|
||||||
import org.apache.http.util.EntityUtils;
|
import org.apache.http.util.EntityUtils;
|
||||||
|
import org.apache.wicket.util.io.IOUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Smart HTTP getter: handles gzip, ssl, last modified and etag headers
|
* Smart HTTP getter: handles gzip, ssl, last modified and etag headers
|
||||||
@@ -52,8 +55,32 @@ public class HttpGetter {
|
|||||||
private static final String ACCEPT_LANGUAGE = "en";
|
private static final String ACCEPT_LANGUAGE = "en";
|
||||||
private static final String PRAGMA_NO_CACHE = "No-cache";
|
private static final String PRAGMA_NO_CACHE = "No-cache";
|
||||||
private static final String CACHE_CONTROL_NO_CACHE = "no-cache";
|
private static final String CACHE_CONTROL_NO_CACHE = "no-cache";
|
||||||
private static final String UTF8 = "UTF-8";
|
|
||||||
private static final String HTTPS = "https";
|
private static final List<String> ALLOWED_CONTENT_ENCODINGS = Arrays.asList("gzip", "x-gzip", "deflate", "identity");
|
||||||
|
private static final HttpResponseInterceptor REMOVE_INCORRECT_CONTENT_ENCODING = new HttpResponseInterceptor() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process(HttpResponse response, HttpContext context) throws HttpException, IOException {
|
||||||
|
HttpEntity entity = response.getEntity();
|
||||||
|
if (entity != null && entity.getContentLength() != 0) {
|
||||||
|
Header header = entity.getContentEncoding();
|
||||||
|
if (header != null) {
|
||||||
|
HeaderElement[] codecs = header.getElements();
|
||||||
|
for (final HeaderElement codec : codecs) {
|
||||||
|
String codecName = codec.getName().toLowerCase(Locale.US);
|
||||||
|
if (!ALLOWED_CONTENT_ENCODINGS.contains(codecName)) {
|
||||||
|
response.setEntity(new HttpEntityWrapper(entity) {
|
||||||
|
@Override
|
||||||
|
public Header getContentEncoding() {
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private static SSLContext SSL_CONTEXT = null;
|
private static SSLContext SSL_CONTEXT = null;
|
||||||
static {
|
static {
|
||||||
@@ -65,8 +92,6 @@ public class HttpGetter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final X509HostnameVerifier VERIFIER = new DefaultHostnameVerifier();
|
|
||||||
|
|
||||||
public HttpResult getBinary(String url, int timeout) throws ClientProtocolException, IOException, NotModifiedException {
|
public HttpResult getBinary(String url, int timeout) throws ClientProtocolException, IOException, NotModifiedException {
|
||||||
return getBinary(url, null, null, timeout);
|
return getBinary(url, null, null, timeout);
|
||||||
}
|
}
|
||||||
@@ -90,9 +115,12 @@ public class HttpGetter {
|
|||||||
HttpResult result = null;
|
HttpResult result = null;
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
HttpClient client = newClient(timeout);
|
CloseableHttpClient client = newClient(timeout);
|
||||||
|
CloseableHttpResponse response = null;
|
||||||
try {
|
try {
|
||||||
HttpGet httpget = new HttpGet(url);
|
HttpGet httpget = new HttpGet(url);
|
||||||
|
HttpClientContext context = HttpClientContext.create();
|
||||||
|
|
||||||
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);
|
||||||
@@ -105,9 +133,8 @@ public class HttpGetter {
|
|||||||
httpget.addHeader(HttpHeaders.IF_NONE_MATCH, eTag);
|
httpget.addHeader(HttpHeaders.IF_NONE_MATCH, eTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +150,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,12 +170,15 @@ public class HttpGetter {
|
|||||||
contentType = entity.getContentType().getValue();
|
contentType = entity.getContentType().getValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
HttpUriRequest req = (HttpUriRequest) context.getRequest();
|
||||||
|
HttpHost host = context.getTargetHost();
|
||||||
|
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();
|
IOUtils.closeQuietly(response);
|
||||||
|
IOUtils.closeQuietly(client);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -161,13 +190,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,23 +221,30 @@ public class HttpGetter {
|
|||||||
return duration;
|
return duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getUrlAfterRedirect() {
|
||||||
|
return urlAfterRedirect;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static HttpClient newClient(int timeout) {
|
public static CloseableHttpClient newClient(int timeout) {
|
||||||
DefaultHttpClient client = new SystemDefaultHttpClient();
|
HttpClientBuilder builder = HttpClients.custom();
|
||||||
|
builder.useSystemProperties();
|
||||||
|
builder.addInterceptorFirst(REMOVE_INCORRECT_CONTENT_ENCODING);
|
||||||
|
builder.disableAutomaticRetries();
|
||||||
|
|
||||||
SSLSocketFactory ssf = new SSLSocketFactory(SSL_CONTEXT, VERIFIER);
|
builder.setSslcontext(SSL_CONTEXT);
|
||||||
ClientConnectionManager ccm = client.getConnectionManager();
|
builder.setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
|
||||||
SchemeRegistry sr = ccm.getSchemeRegistry();
|
|
||||||
sr.register(new Scheme(HTTPS, 443, ssf));
|
|
||||||
|
|
||||||
HttpParams params = client.getParams();
|
RequestConfig.Builder configBuilder = RequestConfig.custom();
|
||||||
HttpClientParams.setCookiePolicy(params, CookiePolicy.IGNORE_COOKIES);
|
configBuilder.setCookieSpec(CookieSpecs.IGNORE_COOKIES);
|
||||||
HttpProtocolParams.setContentCharset(params, UTF8);
|
configBuilder.setSocketTimeout(timeout);
|
||||||
HttpConnectionParams.setConnectionTimeout(params, timeout);
|
configBuilder.setConnectTimeout(timeout);
|
||||||
HttpConnectionParams.setSoTimeout(params, timeout);
|
configBuilder.setConnectionRequestTimeout(timeout);
|
||||||
client.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(0, false));
|
builder.setDefaultRequestConfig(configBuilder.build());
|
||||||
return new DecompressingHttpClient(client);
|
|
||||||
|
builder.setDefaultConnectionConfig(ConnectionConfig.custom().setCharset(Consts.ISO_8859_1).build());
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class NotModifiedException extends Exception {
|
public static class NotModifiedException extends Exception {
|
||||||
@@ -232,24 +270,4 @@ public class HttpGetter {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class DefaultHostnameVerifier implements X509HostnameVerifier {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void verify(String string, SSLSocket ssls) throws IOException {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void verify(String string, X509Certificate xc) throws SSLException {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void verify(String string, String[] strings, String[] strings1) throws SSLException {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean verify(String string, SSLSession ssls) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,36 +4,45 @@ 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
|
||||||
*/
|
*/
|
||||||
@Schedule(hour = "0", persistent = false)
|
@Schedule(hour = "*", persistent = false)
|
||||||
private void cleanupOldStatuses() {
|
private void cleanupOldStatuses() {
|
||||||
Date threshold = applicationSettingsService.getUnreadThreshold();
|
Date threshold = applicationSettingsService.getUnreadThreshold();
|
||||||
if (threshold != null) {
|
if (threshold != null) {
|
||||||
cleaner.cleanStatusesOlderThan(threshold);
|
cleaner.cleanStatusesOlderThan(threshold);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* clean feeds without subscriptions, then clean contents without entries
|
||||||
|
*/
|
||||||
|
@Schedule(hour = "*", persistent = false)
|
||||||
|
private void cleanFeedsAndContents() {
|
||||||
|
cleaner.cleanEntriesWithoutSubscriptions();
|
||||||
|
cleaner.cleanFeedsWithoutSubscriptions();
|
||||||
|
cleaner.cleanContentsWithoutEntries();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ import java.util.List;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
import javax.enterprise.context.ApplicationScoped;
|
import javax.enterprise.context.ApplicationScoped;
|
||||||
import javax.enterprise.inject.Alternative;
|
import javax.enterprise.inject.Alternative;
|
||||||
|
|
||||||
|
import org.apache.commons.pool.impl.GenericObjectPool;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import redis.clients.jedis.Jedis;
|
import redis.clients.jedis.Jedis;
|
||||||
import redis.clients.jedis.JedisPool;
|
import redis.clients.jedis.JedisPool;
|
||||||
@@ -30,7 +33,14 @@ public class RedisCacheService extends CacheService {
|
|||||||
|
|
||||||
private static ObjectMapper mapper = new ObjectMapper();
|
private static ObjectMapper mapper = new ObjectMapper();
|
||||||
|
|
||||||
private JedisPool pool = new JedisPool(new JedisPoolConfig(), "localhost");
|
private JedisPool pool;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
private void init() {
|
||||||
|
JedisPoolConfig config = new JedisPoolConfig();
|
||||||
|
config.setWhenExhaustedAction(GenericObjectPool.WHEN_EXHAUSTED_GROW);
|
||||||
|
pool = new JedisPool(config, "localhost");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> getLastEntries(Feed feed) {
|
public List<String> getLastEntries(Feed feed) {
|
||||||
|
|||||||
@@ -6,15 +6,12 @@ import java.util.List;
|
|||||||
import javax.ejb.Stateless;
|
import javax.ejb.Stateless;
|
||||||
import javax.persistence.TypedQuery;
|
import javax.persistence.TypedQuery;
|
||||||
import javax.persistence.criteria.CriteriaQuery;
|
import javax.persistence.criteria.CriteriaQuery;
|
||||||
import javax.persistence.criteria.Expression;
|
|
||||||
import javax.persistence.criteria.Join;
|
import javax.persistence.criteria.Join;
|
||||||
import javax.persistence.criteria.JoinType;
|
import javax.persistence.criteria.JoinType;
|
||||||
import javax.persistence.criteria.Path;
|
|
||||||
import javax.persistence.criteria.Predicate;
|
import javax.persistence.criteria.Predicate;
|
||||||
import javax.persistence.criteria.Root;
|
import javax.persistence.criteria.Root;
|
||||||
import javax.persistence.criteria.SetJoin;
|
import javax.persistence.criteria.SetJoin;
|
||||||
import javax.persistence.criteria.Subquery;
|
import javax.persistence.criteria.Subquery;
|
||||||
import javax.persistence.metamodel.SingularAttribute;
|
|
||||||
|
|
||||||
import org.apache.commons.codec.digest.DigestUtils;
|
import org.apache.commons.codec.digest.DigestUtils;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
@@ -26,7 +23,6 @@ import com.commafeed.backend.model.FeedSubscription_;
|
|||||||
import com.commafeed.backend.model.Feed_;
|
import com.commafeed.backend.model.Feed_;
|
||||||
import com.commafeed.backend.model.User;
|
import com.commafeed.backend.model.User;
|
||||||
import com.commafeed.backend.model.User_;
|
import com.commafeed.backend.model.User_;
|
||||||
import com.commafeed.frontend.model.FeedCount;
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
@@ -95,8 +91,8 @@ public class FeedDAO extends GenericDAO<Feed> {
|
|||||||
public List<Feed> findByTopic(String topic) {
|
public List<Feed> findByTopic(String topic) {
|
||||||
return findByField(Feed_.pushTopicHash, DigestUtils.sha1Hex(topic));
|
return findByField(Feed_.pushTopicHash, DigestUtils.sha1Hex(topic));
|
||||||
}
|
}
|
||||||
|
|
||||||
public int deleteWithoutSubscriptions(int max) {
|
public List<Feed> findWithoutSubscriptions(int max) {
|
||||||
CriteriaQuery<Feed> query = builder.createQuery(getType());
|
CriteriaQuery<Feed> query = builder.createQuery(getType());
|
||||||
Root<Feed> root = query.from(getType());
|
Root<Feed> root = query.from(getType());
|
||||||
|
|
||||||
@@ -105,54 +101,6 @@ public class FeedDAO extends GenericDAO<Feed> {
|
|||||||
TypedQuery<Feed> q = em.createQuery(query);
|
TypedQuery<Feed> q = em.createQuery(query);
|
||||||
q.setMaxResults(max);
|
q.setMaxResults(max);
|
||||||
|
|
||||||
List<Feed> list = q.getResultList();
|
return q.getResultList();
|
||||||
int deleted = list.size();
|
|
||||||
|
|
||||||
delete(list);
|
|
||||||
return deleted;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static enum DuplicateMode {
|
|
||||||
NORMALIZED_URL(Feed_.normalizedUrlHash), LAST_CONTENT(Feed_.lastContentHash), PUSH_TOPIC(Feed_.pushTopicHash);
|
|
||||||
private SingularAttribute<Feed, String> path;
|
|
||||||
|
|
||||||
private DuplicateMode(SingularAttribute<Feed, String> path) {
|
|
||||||
this.path = path;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SingularAttribute<Feed, String> getPath() {
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<FeedCount> findDuplicates(DuplicateMode mode, int offset, int limit, long minCount) {
|
|
||||||
CriteriaQuery<String> query = builder.createQuery(String.class);
|
|
||||||
Root<Feed> root = query.from(getType());
|
|
||||||
|
|
||||||
Path<String> path = root.get(mode.getPath());
|
|
||||||
Expression<Long> count = builder.count(path);
|
|
||||||
|
|
||||||
query.select(path);
|
|
||||||
|
|
||||||
query.groupBy(path);
|
|
||||||
query.having(builder.greaterThan(count, minCount));
|
|
||||||
|
|
||||||
TypedQuery<String> q = em.createQuery(query);
|
|
||||||
limit(q, offset, limit);
|
|
||||||
List<String> pathValues = q.getResultList();
|
|
||||||
|
|
||||||
List<FeedCount> result = Lists.newArrayList();
|
|
||||||
for (String pathValue : pathValues) {
|
|
||||||
FeedCount fc = new FeedCount(pathValue);
|
|
||||||
for (Feed feed : findByField(mode.getPath(), pathValue)) {
|
|
||||||
Feed f = new Feed();
|
|
||||||
f.setId(feed.getId());
|
|
||||||
f.setUrl(feed.getUrl());
|
|
||||||
fc.getFeeds().add(f);
|
|
||||||
}
|
|
||||||
result.add(fc);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.commafeed.backend.dao;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.ejb.Stateless;
|
||||||
import javax.persistence.TypedQuery;
|
import javax.persistence.TypedQuery;
|
||||||
import javax.persistence.criteria.CriteriaQuery;
|
import javax.persistence.criteria.CriteriaQuery;
|
||||||
import javax.persistence.criteria.Join;
|
import javax.persistence.criteria.Join;
|
||||||
@@ -15,6 +16,7 @@ import com.commafeed.backend.model.FeedEntryContent_;
|
|||||||
import com.commafeed.backend.model.FeedEntry_;
|
import com.commafeed.backend.model.FeedEntry_;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
@Stateless
|
||||||
public class FeedEntryContentDAO extends GenericDAO<FeedEntryContent> {
|
public class FeedEntryContentDAO extends GenericDAO<FeedEntryContent> {
|
||||||
|
|
||||||
public Long findExisting(String contentHash, String titleHash) {
|
public Long findExisting(String contentHash, String titleHash) {
|
||||||
@@ -44,6 +46,7 @@ public class FeedEntryContentDAO extends GenericDAO<FeedEntryContent> {
|
|||||||
|
|
||||||
List<FeedEntryContent> list = q.getResultList();
|
List<FeedEntryContent> list = q.getResultList();
|
||||||
int deleted = list.size();
|
int deleted = list.size();
|
||||||
|
delete(list);
|
||||||
return deleted;
|
return deleted;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,19 @@ import java.util.List;
|
|||||||
import javax.ejb.Stateless;
|
import javax.ejb.Stateless;
|
||||||
import javax.persistence.TypedQuery;
|
import javax.persistence.TypedQuery;
|
||||||
import javax.persistence.criteria.CriteriaQuery;
|
import javax.persistence.criteria.CriteriaQuery;
|
||||||
|
import javax.persistence.criteria.Join;
|
||||||
|
import javax.persistence.criteria.JoinType;
|
||||||
import javax.persistence.criteria.Predicate;
|
import javax.persistence.criteria.Predicate;
|
||||||
import javax.persistence.criteria.Root;
|
import javax.persistence.criteria.Root;
|
||||||
|
import javax.persistence.criteria.SetJoin;
|
||||||
|
|
||||||
import org.apache.commons.codec.digest.DigestUtils;
|
import org.apache.commons.codec.digest.DigestUtils;
|
||||||
|
|
||||||
|
import com.commafeed.backend.model.Feed;
|
||||||
import com.commafeed.backend.model.FeedEntry;
|
import com.commafeed.backend.model.FeedEntry;
|
||||||
import com.commafeed.backend.model.FeedEntry_;
|
import com.commafeed.backend.model.FeedEntry_;
|
||||||
|
import com.commafeed.backend.model.FeedSubscription;
|
||||||
|
import com.commafeed.backend.model.FeedSubscription_;
|
||||||
import com.commafeed.backend.model.Feed_;
|
import com.commafeed.backend.model.Feed_;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
@@ -36,6 +42,34 @@ public class FeedEntryDAO extends GenericDAO<FeedEntry> {
|
|||||||
return Iterables.getFirst(list, null);
|
return Iterables.getFirst(list, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<FeedEntry> findWithoutSubscriptions(int max) {
|
||||||
|
CriteriaQuery<FeedEntry> query = builder.createQuery(getType());
|
||||||
|
Root<FeedEntry> root = query.from(getType());
|
||||||
|
|
||||||
|
Join<FeedEntry, Feed> feedJoin = root.join(FeedEntry_.feed);
|
||||||
|
SetJoin<Feed, FeedSubscription> subJoin = feedJoin.join(Feed_.subscriptions, JoinType.LEFT);
|
||||||
|
query.where(builder.isNull(subJoin.get(FeedSubscription_.id)));
|
||||||
|
TypedQuery<FeedEntry> q = em.createQuery(query);
|
||||||
|
q.setMaxResults(max);
|
||||||
|
|
||||||
|
return q.getResultList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int delete(Feed feed, int max) {
|
||||||
|
|
||||||
|
CriteriaQuery<FeedEntry> query = builder.createQuery(getType());
|
||||||
|
Root<FeedEntry> root = query.from(getType());
|
||||||
|
|
||||||
|
query.where(builder.equal(root.get(FeedEntry_.feed), feed));
|
||||||
|
TypedQuery<FeedEntry> q = em.createQuery(query);
|
||||||
|
q.setMaxResults(max);
|
||||||
|
|
||||||
|
List<FeedEntry> list = q.getResultList();
|
||||||
|
int deleted = list.size();
|
||||||
|
delete(list);
|
||||||
|
return deleted;
|
||||||
|
}
|
||||||
|
|
||||||
public int delete(Date olderThan, int max) {
|
public int delete(Date olderThan, int max) {
|
||||||
CriteriaQuery<FeedEntry> query = builder.createQuery(getType());
|
CriteriaQuery<FeedEntry> query = builder.createQuery(getType());
|
||||||
Root<FeedEntry> root = query.from(getType());
|
Root<FeedEntry> root = query.from(getType());
|
||||||
|
|||||||
@@ -15,8 +15,9 @@ import javax.persistence.criteria.Predicate;
|
|||||||
import javax.persistence.criteria.Root;
|
import javax.persistence.criteria.Root;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
import org.apache.commons.lang3.builder.CompareToBuilder;
|
||||||
import org.hibernate.Criteria;
|
import org.hibernate.Criteria;
|
||||||
|
import org.hibernate.criterion.Conjunction;
|
||||||
import org.hibernate.criterion.Disjunction;
|
import org.hibernate.criterion.Disjunction;
|
||||||
import org.hibernate.criterion.MatchMode;
|
import org.hibernate.criterion.MatchMode;
|
||||||
import org.hibernate.criterion.Order;
|
import org.hibernate.criterion.Order;
|
||||||
@@ -31,6 +32,8 @@ import com.commafeed.backend.model.FeedEntry;
|
|||||||
import com.commafeed.backend.model.FeedEntryContent_;
|
import com.commafeed.backend.model.FeedEntryContent_;
|
||||||
import com.commafeed.backend.model.FeedEntryStatus;
|
import com.commafeed.backend.model.FeedEntryStatus;
|
||||||
import com.commafeed.backend.model.FeedEntryStatus_;
|
import com.commafeed.backend.model.FeedEntryStatus_;
|
||||||
|
import com.commafeed.backend.model.FeedEntryTag;
|
||||||
|
import com.commafeed.backend.model.FeedEntryTag_;
|
||||||
import com.commafeed.backend.model.FeedEntry_;
|
import com.commafeed.backend.model.FeedEntry_;
|
||||||
import com.commafeed.backend.model.FeedSubscription;
|
import com.commafeed.backend.model.FeedSubscription;
|
||||||
import com.commafeed.backend.model.Models;
|
import com.commafeed.backend.model.Models;
|
||||||
@@ -40,31 +43,34 @@ import com.commafeed.backend.services.ApplicationSettingsService;
|
|||||||
import com.commafeed.frontend.model.UnreadCount;
|
import com.commafeed.frontend.model.UnreadCount;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Ordering;
|
||||||
|
|
||||||
@Stateless
|
@Stateless
|
||||||
public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
||||||
|
|
||||||
private static final String ALIAS_STATUS = "status";
|
private static final String ALIAS_STATUS = "status";
|
||||||
private static final String ALIAS_ENTRY = "entry";
|
private static final String ALIAS_ENTRY = "entry";
|
||||||
|
private static final String ALIAS_TAG = "tag";
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FeedEntryTagDAO feedEntryTagDAO;
|
||||||
|
|
||||||
private static final Comparator<FeedEntryStatus> STATUS_COMPARATOR_DESC = new Comparator<FeedEntryStatus>() {
|
private static final Comparator<FeedEntryStatus> STATUS_COMPARATOR_DESC = new Comparator<FeedEntryStatus>() {
|
||||||
@Override
|
@Override
|
||||||
public int compare(FeedEntryStatus o1, FeedEntryStatus o2) {
|
public int compare(FeedEntryStatus o1, FeedEntryStatus o2) {
|
||||||
return ObjectUtils.compare(o2.getEntryUpdated(), o1.getEntryUpdated());
|
CompareToBuilder builder = new CompareToBuilder();
|
||||||
|
builder.append(o2.getEntryUpdated(), o1.getEntryUpdated());
|
||||||
|
builder.append(o2.getId(), o1.getId());
|
||||||
|
return builder.build();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final Comparator<FeedEntryStatus> STATUS_COMPARATOR_ASC = new Comparator<FeedEntryStatus>() {
|
private static final Comparator<FeedEntryStatus> STATUS_COMPARATOR_ASC = Ordering.from(STATUS_COMPARATOR_DESC).reverse();
|
||||||
@Override
|
|
||||||
public int compare(FeedEntryStatus o1, FeedEntryStatus o2) {
|
|
||||||
return ObjectUtils.compare(o1.getEntryUpdated(), o2.getEntryUpdated());
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ApplicationSettingsService applicationSettingsService;
|
ApplicationSettingsService applicationSettingsService;
|
||||||
|
|
||||||
public FeedEntryStatus getStatus(FeedSubscription sub, FeedEntry entry) {
|
public FeedEntryStatus getStatus(User user, FeedSubscription sub, FeedEntry entry) {
|
||||||
|
|
||||||
CriteriaQuery<FeedEntryStatus> query = builder.createQuery(getType());
|
CriteriaQuery<FeedEntryStatus> query = builder.createQuery(getType());
|
||||||
Root<FeedEntryStatus> root = query.from(getType());
|
Root<FeedEntryStatus> root = query.from(getType());
|
||||||
@@ -77,14 +83,14 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
|||||||
List<FeedEntryStatus> statuses = em.createQuery(query).getResultList();
|
List<FeedEntryStatus> statuses = em.createQuery(query).getResultList();
|
||||||
FeedEntryStatus status = Iterables.getFirst(statuses, null);
|
FeedEntryStatus status = Iterables.getFirst(statuses, null);
|
||||||
|
|
||||||
return handleStatus(status, sub, entry);
|
return handleStatus(user, status, sub, entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
private FeedEntryStatus handleStatus(FeedEntryStatus status, FeedSubscription sub, FeedEntry entry) {
|
private FeedEntryStatus handleStatus(User user, FeedEntryStatus status, FeedSubscription sub, FeedEntry entry) {
|
||||||
if (status == null) {
|
if (status == null) {
|
||||||
Date unreadThreshold = applicationSettingsService.getUnreadThreshold();
|
Date unreadThreshold = applicationSettingsService.getUnreadThreshold();
|
||||||
boolean read = unreadThreshold == null ? false : entry.getUpdated().before(unreadThreshold);
|
boolean read = unreadThreshold == null ? false : entry.getUpdated().before(unreadThreshold);
|
||||||
status = new FeedEntryStatus(sub.getUser(), sub, entry);
|
status = new FeedEntryStatus(user, sub, entry);
|
||||||
status.setRead(read);
|
status.setRead(read);
|
||||||
status.setMarkable(!read);
|
status.setMarkable(!read);
|
||||||
} else {
|
} else {
|
||||||
@@ -93,6 +99,12 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private FeedEntryStatus fetchTags(User user, FeedEntryStatus status) {
|
||||||
|
List<FeedEntryTag> tags = feedEntryTagDAO.findByEntry(user, status.getEntry());
|
||||||
|
status.setTags(tags);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
public List<FeedEntryStatus> findStarred(User user, Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent) {
|
public List<FeedEntryStatus> findStarred(User user, Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent) {
|
||||||
|
|
||||||
CriteriaQuery<FeedEntryStatus> query = builder.createQuery(getType());
|
CriteriaQuery<FeedEntryStatus> query = builder.createQuery(getType());
|
||||||
@@ -102,11 +114,12 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
|||||||
|
|
||||||
predicates.add(builder.equal(root.get(FeedEntryStatus_.user), user));
|
predicates.add(builder.equal(root.get(FeedEntryStatus_.user), user));
|
||||||
predicates.add(builder.equal(root.get(FeedEntryStatus_.starred), true));
|
predicates.add(builder.equal(root.get(FeedEntryStatus_.starred), true));
|
||||||
query.where(predicates.toArray(new Predicate[0]));
|
|
||||||
|
|
||||||
if (newerThan != null) {
|
if (newerThan != null) {
|
||||||
predicates.add(builder.greaterThanOrEqualTo(root.get(FeedEntryStatus_.entryInserted), newerThan));
|
predicates.add(builder.greaterThanOrEqualTo(root.get(FeedEntryStatus_.entryInserted), newerThan));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
query.where(predicates.toArray(new Predicate[0]));
|
||||||
|
|
||||||
orderStatusesBy(query, root, order);
|
orderStatusesBy(query, root, order);
|
||||||
|
|
||||||
@@ -115,13 +128,14 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
|||||||
setTimeout(q);
|
setTimeout(q);
|
||||||
List<FeedEntryStatus> statuses = q.getResultList();
|
List<FeedEntryStatus> statuses = q.getResultList();
|
||||||
for (FeedEntryStatus status : statuses) {
|
for (FeedEntryStatus status : statuses) {
|
||||||
status = handleStatus(status, status.getSubscription(), status.getEntry());
|
status = handleStatus(user, status, status.getSubscription(), status.getEntry());
|
||||||
|
status = fetchTags(user, status);
|
||||||
}
|
}
|
||||||
return lazyLoadContent(includeContent, statuses);
|
return lazyLoadContent(includeContent, statuses);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Criteria buildSearchCriteria(FeedSubscription sub, boolean unreadOnly, String keywords, Date newerThan, int offset, int limit,
|
private Criteria buildSearchCriteria(User user, FeedSubscription sub, boolean unreadOnly, String keywords, Date newerThan, int offset, int limit,
|
||||||
ReadingOrder order, Date last) {
|
ReadingOrder order, Date last, String tag) {
|
||||||
Criteria criteria = getSession().createCriteria(FeedEntry.class, ALIAS_ENTRY);
|
Criteria criteria = getSession().createCriteria(FeedEntry.class, ALIAS_ENTRY);
|
||||||
|
|
||||||
criteria.add(Restrictions.eq(FeedEntry_.feed.getName(), sub.getFeed()));
|
criteria.add(Restrictions.eq(FeedEntry_.feed.getName(), sub.getFeed()));
|
||||||
@@ -139,7 +153,7 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
|||||||
Criteria statusJoin = criteria.createCriteria(FeedEntry_.statuses.getName(), ALIAS_STATUS, JoinType.LEFT_OUTER_JOIN,
|
Criteria statusJoin = criteria.createCriteria(FeedEntry_.statuses.getName(), ALIAS_STATUS, JoinType.LEFT_OUTER_JOIN,
|
||||||
Restrictions.eq(FeedEntryStatus_.subscription.getName(), sub));
|
Restrictions.eq(FeedEntryStatus_.subscription.getName(), sub));
|
||||||
|
|
||||||
if (unreadOnly) {
|
if (unreadOnly && tag == null) {
|
||||||
|
|
||||||
Disjunction or = Restrictions.disjunction();
|
Disjunction or = Restrictions.disjunction();
|
||||||
or.add(Restrictions.isNull(FeedEntryStatus_.read.getName()));
|
or.add(Restrictions.isNull(FeedEntryStatus_.read.getName()));
|
||||||
@@ -152,6 +166,13 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tag != null) {
|
||||||
|
Conjunction and = Restrictions.conjunction();
|
||||||
|
and.add(Restrictions.eq(FeedEntryTag_.user.getName(), user));
|
||||||
|
and.add(Restrictions.eq(FeedEntryTag_.name.getName(), tag));
|
||||||
|
criteria.createCriteria(FeedEntry_.tags.getName(), ALIAS_TAG, JoinType.INNER_JOIN, and);
|
||||||
|
}
|
||||||
|
|
||||||
if (newerThan != null) {
|
if (newerThan != null) {
|
||||||
criteria.add(Restrictions.ge(FeedEntry_.inserted.getName(), newerThan));
|
criteria.add(Restrictions.ge(FeedEntry_.inserted.getName(), newerThan));
|
||||||
}
|
}
|
||||||
@@ -165,13 +186,11 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (order != null) {
|
if (order != null) {
|
||||||
Order o = null;
|
|
||||||
if (order == ReadingOrder.asc) {
|
if (order == ReadingOrder.asc) {
|
||||||
o = Order.asc(FeedEntry_.updated.getName());
|
criteria.addOrder(Order.asc(FeedEntry_.updated.getName())).addOrder(Order.asc(FeedEntry_.id.getName()));
|
||||||
} else {
|
} else {
|
||||||
o = Order.desc(FeedEntry_.updated.getName());
|
criteria.addOrder(Order.desc(FeedEntry_.updated.getName())).addOrder(Order.desc(FeedEntry_.id.getName()));
|
||||||
}
|
}
|
||||||
criteria.addOrder(o);
|
|
||||||
}
|
}
|
||||||
if (offset > -1) {
|
if (offset > -1) {
|
||||||
criteria.setFirstResult(offset);
|
criteria.setFirstResult(offset);
|
||||||
@@ -188,14 +207,14 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public List<FeedEntryStatus> findBySubscriptions(List<FeedSubscription> subs, boolean unreadOnly, String keywords, Date newerThan,
|
public List<FeedEntryStatus> findBySubscriptions(User user, List<FeedSubscription> subs, boolean unreadOnly, String keywords,
|
||||||
int offset, int limit, ReadingOrder order, boolean includeContent, boolean onlyIds) {
|
Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent, boolean onlyIds, String tag) {
|
||||||
int capacity = offset + limit;
|
int capacity = offset + limit;
|
||||||
Comparator<FeedEntryStatus> comparator = order == ReadingOrder.desc ? STATUS_COMPARATOR_DESC : STATUS_COMPARATOR_ASC;
|
Comparator<FeedEntryStatus> comparator = order == ReadingOrder.desc ? STATUS_COMPARATOR_DESC : STATUS_COMPARATOR_ASC;
|
||||||
FixedSizeSortedSet<FeedEntryStatus> set = new FixedSizeSortedSet<FeedEntryStatus>(capacity, comparator);
|
FixedSizeSortedSet<FeedEntryStatus> set = new FixedSizeSortedSet<FeedEntryStatus>(capacity, comparator);
|
||||||
for (FeedSubscription sub : subs) {
|
for (FeedSubscription sub : subs) {
|
||||||
Date last = (order != null && set.isFull()) ? set.last().getEntryUpdated() : null;
|
Date last = (order != null && set.isFull()) ? set.last().getEntryUpdated() : null;
|
||||||
Criteria criteria = buildSearchCriteria(sub, unreadOnly, keywords, newerThan, -1, capacity, order, last);
|
Criteria criteria = buildSearchCriteria(user, sub, unreadOnly, keywords, newerThan, -1, capacity, order, last, tag);
|
||||||
ProjectionList projection = Projections.projectionList();
|
ProjectionList projection = Projections.projectionList();
|
||||||
projection.add(Projections.property("id"), "id");
|
projection.add(Projections.property("id"), "id");
|
||||||
projection.add(Projections.property("updated"), "updated");
|
projection.add(Projections.property("updated"), "updated");
|
||||||
@@ -237,7 +256,10 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
|||||||
for (FeedEntryStatus placeholder : placeholders) {
|
for (FeedEntryStatus placeholder : placeholders) {
|
||||||
Long statusId = placeholder.getId();
|
Long statusId = placeholder.getId();
|
||||||
FeedEntry entry = em.find(FeedEntry.class, placeholder.getEntry().getId());
|
FeedEntry entry = em.find(FeedEntry.class, placeholder.getEntry().getId());
|
||||||
statuses.add(handleStatus(statusId == null ? null : findById(statusId), placeholder.getSubscription(), entry));
|
FeedEntryStatus status = handleStatus(user, statusId == null ? null : findById(statusId), placeholder.getSubscription(),
|
||||||
|
entry);
|
||||||
|
status = fetchTags(user, status);
|
||||||
|
statuses.add(status);
|
||||||
}
|
}
|
||||||
statuses = lazyLoadContent(includeContent, statuses);
|
statuses = lazyLoadContent(includeContent, statuses);
|
||||||
}
|
}
|
||||||
@@ -245,9 +267,9 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public UnreadCount getUnreadCount(FeedSubscription subscription) {
|
public UnreadCount getUnreadCount(User user, FeedSubscription subscription) {
|
||||||
UnreadCount uc = null;
|
UnreadCount uc = null;
|
||||||
Criteria criteria = buildSearchCriteria(subscription, true, null, null, -1, -1, null, null);
|
Criteria criteria = buildSearchCriteria(user, subscription, true, null, null, -1, -1, null, null, null);
|
||||||
ProjectionList projection = Projections.projectionList();
|
ProjectionList projection = Projections.projectionList();
|
||||||
projection.add(Projections.rowCount(), "count");
|
projection.add(Projections.rowCount(), "count");
|
||||||
projection.add(Projections.max(FeedEntry_.updated.getName()), "updated");
|
projection.add(Projections.max(FeedEntry_.updated.getName()), "updated");
|
||||||
@@ -273,15 +295,15 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void orderStatusesBy(CriteriaQuery<?> query, Path<FeedEntryStatus> statusJoin, ReadingOrder order) {
|
private void orderStatusesBy(CriteriaQuery<?> query, Path<FeedEntryStatus> statusJoin, ReadingOrder order) {
|
||||||
orderBy(query, statusJoin.get(FeedEntryStatus_.entryUpdated), order);
|
orderBy(query, statusJoin.get(FeedEntryStatus_.entryUpdated), statusJoin.get(FeedEntryStatus_.id), order);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void orderBy(CriteriaQuery<?> query, Path<Date> date, ReadingOrder order) {
|
private void orderBy(CriteriaQuery<?> query, Path<Date> date, Path<Long> id, ReadingOrder order) {
|
||||||
if (order != null) {
|
if (order != null) {
|
||||||
if (order == ReadingOrder.asc) {
|
if (order == ReadingOrder.asc) {
|
||||||
query.orderBy(builder.asc(date));
|
query.orderBy(builder.asc(date), builder.asc(id));
|
||||||
} else {
|
} else {
|
||||||
query.orderBy(builder.desc(date));
|
query.orderBy(builder.desc(date), builder.desc(id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -290,10 +312,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
43
src/main/java/com/commafeed/backend/dao/FeedEntryTagDAO.java
Normal file
43
src/main/java/com/commafeed/backend/dao/FeedEntryTagDAO.java
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package com.commafeed.backend.dao;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.ejb.Stateless;
|
||||||
|
import javax.persistence.criteria.CriteriaQuery;
|
||||||
|
import javax.persistence.criteria.Predicate;
|
||||||
|
import javax.persistence.criteria.Root;
|
||||||
|
|
||||||
|
import com.commafeed.backend.model.FeedEntry;
|
||||||
|
import com.commafeed.backend.model.FeedEntryTag;
|
||||||
|
import com.commafeed.backend.model.FeedEntryTag_;
|
||||||
|
import com.commafeed.backend.model.FeedEntry_;
|
||||||
|
import com.commafeed.backend.model.User;
|
||||||
|
import com.commafeed.backend.model.User_;
|
||||||
|
|
||||||
|
@Stateless
|
||||||
|
public class FeedEntryTagDAO extends GenericDAO<FeedEntryTag> {
|
||||||
|
|
||||||
|
public List<String> findByUser(User user) {
|
||||||
|
CriteriaQuery<String> query = builder.createQuery(String.class);
|
||||||
|
Root<FeedEntryTag> root = query.from(getType());
|
||||||
|
query.select(root.get(FeedEntryTag_.name));
|
||||||
|
query.distinct(true);
|
||||||
|
|
||||||
|
Predicate p1 = builder.equal(root.get(FeedEntryTag_.user).get(User_.id), user.getId());
|
||||||
|
query.where(p1);
|
||||||
|
|
||||||
|
return cache(em.createQuery(query)).getResultList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FeedEntryTag> findByEntry(User user, FeedEntry entry) {
|
||||||
|
CriteriaQuery<FeedEntryTag> query = builder.createQuery(getType());
|
||||||
|
Root<FeedEntryTag> root = query.from(getType());
|
||||||
|
|
||||||
|
Predicate p1 = builder.equal(root.get(FeedEntryTag_.user).get(User_.id), user.getId());
|
||||||
|
Predicate p2 = builder.equal(root.get(FeedEntryTag_.entry).get(FeedEntry_.id), entry.getId());
|
||||||
|
|
||||||
|
query.where(p1, p2);
|
||||||
|
|
||||||
|
return cache(em.createQuery(query)).getResultList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -63,10 +63,11 @@ public abstract class GenericDAO<T extends AbstractModel> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void delete(Collection<? extends AbstractModel> objects) {
|
public int delete(Collection<? extends AbstractModel> objects) {
|
||||||
for (AbstractModel object : objects) {
|
for (AbstractModel object : objects) {
|
||||||
delete(object);
|
delete(object);
|
||||||
}
|
}
|
||||||
|
return objects.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteById(Long id) {
|
public void deleteById(Long id) {
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ public class FeedFetcher {
|
|||||||
result = getter.getBinary(extractedUrl, lastModified, eTag, timeout);
|
result = getter.getBinary(extractedUrl, lastModified, eTag, timeout);
|
||||||
content = result.getContent();
|
content = result.getContent();
|
||||||
fetchedFeed = parser.parse(feedUrl, content);
|
fetchedFeed = parser.parse(feedUrl, content);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
@@ -77,6 +79,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ public class FeedParser {
|
|||||||
if (xmlString == null) {
|
if (xmlString == null) {
|
||||||
throw new FeedException("Input string is null for url " + feedUrl);
|
throw new FeedException("Input string is null for url " + feedUrl);
|
||||||
}
|
}
|
||||||
|
xmlString = FeedUtils.replaceHtmlEntitiesWithNumericEntities(xmlString);
|
||||||
InputSource source = new InputSource(new StringReader(xmlString));
|
InputSource source = new InputSource(new StringReader(xmlString));
|
||||||
SyndFeed rss = new SyndFeedInput().build(source);
|
SyndFeed rss = new SyndFeedInput().build(source);
|
||||||
handleForeignMarkup(rss);
|
handleForeignMarkup(rss);
|
||||||
@@ -66,10 +67,6 @@ public class FeedParser {
|
|||||||
feed.setLink(rss.getLink());
|
feed.setLink(rss.getLink());
|
||||||
List<SyndEntry> items = rss.getEntries();
|
List<SyndEntry> items = rss.getEntries();
|
||||||
|
|
||||||
if (items.isEmpty()) {
|
|
||||||
throw new FeedException("No items in the feed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (SyndEntry item : items) {
|
for (SyndEntry item : items) {
|
||||||
FeedEntry entry = new FeedEntry();
|
FeedEntry entry = new FeedEntry();
|
||||||
|
|
||||||
@@ -82,8 +79,13 @@ public class FeedParser {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
entry.setGuid(FeedUtils.truncate(guid, 2048));
|
entry.setGuid(FeedUtils.truncate(guid, 2048));
|
||||||
entry.setUrl(FeedUtils.truncate(FeedUtils.toAbsoluteUrl(item.getLink(), feed.getLink()), 2048));
|
|
||||||
entry.setUpdated(validateDate(getEntryUpdateDate(item), true));
|
entry.setUpdated(validateDate(getEntryUpdateDate(item), true));
|
||||||
|
entry.setUrl(FeedUtils.truncate(FeedUtils.toAbsoluteUrl(item.getLink(), feed.getLink(), feed.getUrlAfterRedirect()), 2048));
|
||||||
|
|
||||||
|
// if link is empty but guid is used as url
|
||||||
|
if (StringUtils.isBlank(entry.getUrl()) && StringUtils.startsWith(entry.getGuid(), "http")) {
|
||||||
|
entry.setUrl(entry.getGuid());
|
||||||
|
}
|
||||||
|
|
||||||
FeedEntryContent content = new FeedEntryContent();
|
FeedEntryContent content = new FeedEntryContent();
|
||||||
content.setContent(getContent(item));
|
content.setContent(getContent(item));
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ import com.commafeed.backend.model.FeedEntry;
|
|||||||
public class FeedRefreshContext {
|
public class FeedRefreshContext {
|
||||||
private Feed feed;
|
private Feed feed;
|
||||||
private List<FeedEntry> entries;
|
private List<FeedEntry> entries;
|
||||||
private boolean isUrgent;
|
private boolean urgent;
|
||||||
|
|
||||||
public FeedRefreshContext(Feed feed, boolean isUrgent) {
|
public FeedRefreshContext(Feed feed, boolean isUrgent) {
|
||||||
this.feed = feed;
|
this.feed = feed;
|
||||||
this.isUrgent = isUrgent;
|
this.urgent = isUrgent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Feed getFeed() {
|
public Feed getFeed() {
|
||||||
@@ -24,11 +24,11 @@ public class FeedRefreshContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUrgent() {
|
public boolean isUrgent() {
|
||||||
return isUrgent;
|
return urgent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUrgent(boolean isUrgent) {
|
public void setUrgent(boolean urgent) {
|
||||||
this.isUrgent = isUrgent;
|
this.urgent = urgent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<FeedEntry> getEntries() {
|
public List<FeedEntry> getEntries() {
|
||||||
|
|||||||
@@ -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,9 @@ 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.Gauge;
|
||||||
|
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 +43,7 @@ public class FeedRefreshTaskGiver {
|
|||||||
ApplicationSettingsService applicationSettingsService;
|
ApplicationSettingsService applicationSettingsService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
MetricsBean metricsBean;
|
MetricRegistry metrics;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FeedRefreshWorker worker;
|
FeedRefreshWorker worker;
|
||||||
@@ -54,10 +56,35 @@ 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"));
|
||||||
|
metrics.register(MetricRegistry.name(getClass(), "addQueue"), new Gauge<Integer>() {
|
||||||
|
@Override
|
||||||
|
public Integer getValue() {
|
||||||
|
return addQueue.size();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
metrics.register(MetricRegistry.name(getClass(), "takeQueue"), new Gauge<Integer>() {
|
||||||
|
@Override
|
||||||
|
public Integer getValue() {
|
||||||
|
return takeQueue.size();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
metrics.register(MetricRegistry.name(getClass(), "giveBackQueue"), new Gauge<Integer>() {
|
||||||
|
@Override
|
||||||
|
public Integer getValue() {
|
||||||
|
return giveBackQueue.size();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreDestroy
|
@PreDestroy
|
||||||
@@ -73,26 +100,25 @@ public class FeedRefreshTaskGiver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
try {
|
|
||||||
// sleeping for a little while, let everything settle
|
|
||||||
Thread.sleep(5000);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
log.error("interrupted while sleeping");
|
|
||||||
}
|
|
||||||
log.info("starting feed refresh task giver");
|
log.info("starting feed refresh task giver");
|
||||||
|
|
||||||
executor.execute(new Runnable() {
|
executor.execute(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
try {
|
||||||
|
// sleeping for a little while, let everything settle
|
||||||
|
Thread.sleep(60000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
log.error("interrupted while sleeping");
|
||||||
|
}
|
||||||
while (!executor.isShutdown()) {
|
while (!executor.isShutdown()) {
|
||||||
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,22 +164,26 @@ 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() {
|
||||||
int count = Math.min(100, 3 * backgroundThreads);
|
refill.mark();
|
||||||
|
|
||||||
// first, get feeds that are up to refresh from the database
|
|
||||||
List<FeedRefreshContext> contexts = Lists.newArrayList();
|
List<FeedRefreshContext> contexts = Lists.newArrayList();
|
||||||
if (!applicationSettingsService.get().isCrawlingPaused()) {
|
int batchSize = Math.min(100, 3 * backgroundThreads);
|
||||||
List<Feed> feeds = feedDAO.findNextUpdatable(count, getLastLoginThreshold());
|
|
||||||
for (Feed feed : feeds) {
|
// add feeds we got from the add() method
|
||||||
contexts.add(new FeedRefreshContext(feed, false));
|
int addQueueSize = addQueue.size();
|
||||||
}
|
for (int i = 0; i < Math.min(batchSize, addQueueSize); i++) {
|
||||||
|
contexts.add(addQueue.poll());
|
||||||
}
|
}
|
||||||
|
|
||||||
// then, add to those the feeds we got from the add() method. We add them at the beginning of the list as they probably have a
|
// add feeds that are up to refresh from the database
|
||||||
// higher priority
|
if (!applicationSettingsService.get().isCrawlingPaused()) {
|
||||||
int size = addQueue.size();
|
int count = batchSize - contexts.size();
|
||||||
for (int i = 0; i < size; i++) {
|
if (count > 0) {
|
||||||
contexts.add(0, addQueue.poll());
|
List<Feed> feeds = feedDAO.findNextUpdatable(count, getLastLoginThreshold());
|
||||||
|
for (Feed feed : feeds) {
|
||||||
|
contexts.add(new FeedRefreshContext(feed, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the disabledDate to now as we use the disabledDate in feedDAO to decide what to refresh next. We also use a map to remove
|
// set the disabledDate to now as we use the disabledDate in feedDAO to decide what to refresh next. We also use a map to remove
|
||||||
@@ -169,8 +199,8 @@ public class FeedRefreshTaskGiver {
|
|||||||
takeQueue.addAll(map.values());
|
takeQueue.addAll(map.values());
|
||||||
|
|
||||||
// add feeds from the giveBack queue to the map, overriding duplicates
|
// add feeds from the giveBack queue to the map, overriding duplicates
|
||||||
size = giveBackQueue.size();
|
int giveBackQueueSize = giveBackQueue.size();
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < giveBackQueueSize; i++) {
|
||||||
Feed feed = giveBackQueue.poll();
|
Feed feed = giveBackQueue.poll();
|
||||||
map.put(feed.getId(), new FeedRefreshContext(feed, false));
|
map.put(feed.getId(), new FeedRefreshContext(feed, false));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.commafeed.backend.feeds;
|
package com.commafeed.backend.feeds;
|
||||||
|
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@@ -15,6 +17,7 @@ import org.apache.commons.lang.ArrayUtils;
|
|||||||
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 org.apache.commons.math.stat.descriptive.SummaryStatistics;
|
import org.apache.commons.math.stat.descriptive.SummaryStatistics;
|
||||||
|
import org.apache.wicket.request.UrlUtils;
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
import org.jsoup.nodes.Document;
|
import org.jsoup.nodes.Document;
|
||||||
import org.jsoup.nodes.Document.OutputSettings;
|
import org.jsoup.nodes.Document.OutputSettings;
|
||||||
@@ -50,6 +53,8 @@ public class FeedUtils {
|
|||||||
private static final List<String> ALLOWED_IMG_CSS_RULES = Arrays.asList("display", "width", "height");
|
private static final List<String> ALLOWED_IMG_CSS_RULES = Arrays.asList("display", "width", "height");
|
||||||
private static final char[] FORBIDDEN_CSS_RULE_CHARACTERS = new char[] { '(', ')' };
|
private static final char[] FORBIDDEN_CSS_RULE_CHARACTERS = new char[] { '(', ')' };
|
||||||
|
|
||||||
|
private static final Whitelist WHITELIST = buildWhiteList();
|
||||||
|
|
||||||
public static String truncate(String string, int length) {
|
public static String truncate(String string, int length) {
|
||||||
if (string != null) {
|
if (string != null) {
|
||||||
string = string.substring(0, Math.min(length, string.length()));
|
string = string.substring(0, Math.min(length, string.length()));
|
||||||
@@ -57,6 +62,39 @@ public class FeedUtils {
|
|||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static synchronized Whitelist buildWhiteList() {
|
||||||
|
Whitelist whitelist = new Whitelist();
|
||||||
|
whitelist.addTags("a", "b", "blockquote", "br", "caption", "cite", "code", "col", "colgroup", "dd", "div", "dl", "dt", "em", "h1",
|
||||||
|
"h2", "h3", "h4", "h5", "h6", "i", "iframe", "img", "li", "ol", "p", "pre", "q", "small", "strike", "strong", "sub", "sup",
|
||||||
|
"table", "tbody", "td", "tfoot", "th", "thead", "tr", "u", "ul");
|
||||||
|
|
||||||
|
whitelist.addAttributes("div", "dir");
|
||||||
|
whitelist.addAttributes("pre", "dir");
|
||||||
|
whitelist.addAttributes("code", "dir");
|
||||||
|
whitelist.addAttributes("table", "dir");
|
||||||
|
whitelist.addAttributes("p", "dir");
|
||||||
|
whitelist.addAttributes("a", "href", "title");
|
||||||
|
whitelist.addAttributes("blockquote", "cite");
|
||||||
|
whitelist.addAttributes("col", "span", "width");
|
||||||
|
whitelist.addAttributes("colgroup", "span", "width");
|
||||||
|
whitelist.addAttributes("iframe", "src", "height", "width", "allowfullscreen", "frameborder", "style");
|
||||||
|
whitelist.addAttributes("img", "align", "alt", "height", "src", "title", "width", "style");
|
||||||
|
whitelist.addAttributes("ol", "start", "type");
|
||||||
|
whitelist.addAttributes("q", "cite");
|
||||||
|
whitelist.addAttributes("table", "border", "bordercolor", "summary", "width");
|
||||||
|
whitelist.addAttributes("td", "border", "bordercolor", "abbr", "axis", "colspan", "rowspan", "width");
|
||||||
|
whitelist.addAttributes("th", "border", "bordercolor", "abbr", "axis", "colspan", "rowspan", "scope", "width");
|
||||||
|
whitelist.addAttributes("ul", "type");
|
||||||
|
|
||||||
|
whitelist.addProtocols("a", "href", "ftp", "http", "https", "mailto");
|
||||||
|
whitelist.addProtocols("blockquote", "cite", "http", "https");
|
||||||
|
whitelist.addProtocols("img", "src", "http", "https");
|
||||||
|
whitelist.addProtocols("q", "cite", "http", "https");
|
||||||
|
|
||||||
|
whitelist.addEnforcedAttribute("a", "target", "_blank");
|
||||||
|
return whitelist;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detect feed encoding by using the declared encoding in the xml processing instruction and by detecting the characters used in the
|
* Detect feed encoding by using the declared encoding in the xml processing instruction and by detecting the characters used in the
|
||||||
* feed
|
* feed
|
||||||
@@ -91,6 +129,14 @@ public class FeedUtils {
|
|||||||
}
|
}
|
||||||
return encoding;
|
return encoding;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String replaceHtmlEntitiesWithNumericEntities(String source){
|
||||||
|
String result = source;
|
||||||
|
for(String entity : HtmlEntities.NUMERIC_MAPPING.keySet()){
|
||||||
|
result = result.replace(entity, HtmlEntities.NUMERIC_MAPPING.get(entity));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize the url. The resulting url is not meant to be fetched but rather used as a mean to identify a feed and avoid duplicates
|
* Normalize the url. The resulting url is not meant to be fetched but rather used as a mean to identify a feed and avoid duplicates
|
||||||
@@ -152,38 +198,9 @@ public class FeedUtils {
|
|||||||
public static String handleContent(String content, String baseUri, boolean keepTextOnly) {
|
public static String handleContent(String content, String baseUri, boolean keepTextOnly) {
|
||||||
if (StringUtils.isNotBlank(content)) {
|
if (StringUtils.isNotBlank(content)) {
|
||||||
baseUri = StringUtils.trimToEmpty(baseUri);
|
baseUri = StringUtils.trimToEmpty(baseUri);
|
||||||
Whitelist whitelist = new Whitelist();
|
|
||||||
whitelist.addTags("a", "b", "blockquote", "br", "caption", "cite", "code", "col", "colgroup", "dd", "div", "dl", "dt", "em",
|
|
||||||
"h1", "h2", "h3", "h4", "h5", "h6", "i", "iframe", "img", "li", "ol", "p", "pre", "q", "small", "strike", "strong",
|
|
||||||
"sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "u", "ul");
|
|
||||||
|
|
||||||
whitelist.addAttributes("div", "dir");
|
|
||||||
whitelist.addAttributes("pre", "dir");
|
|
||||||
whitelist.addAttributes("code", "dir");
|
|
||||||
whitelist.addAttributes("table", "dir");
|
|
||||||
whitelist.addAttributes("p", "dir");
|
|
||||||
whitelist.addAttributes("a", "href", "title");
|
|
||||||
whitelist.addAttributes("blockquote", "cite");
|
|
||||||
whitelist.addAttributes("col", "span", "width");
|
|
||||||
whitelist.addAttributes("colgroup", "span", "width");
|
|
||||||
whitelist.addAttributes("iframe", "src", "height", "width", "allowfullscreen", "frameborder", "style");
|
|
||||||
whitelist.addAttributes("img", "align", "alt", "height", "src", "title", "width", "style");
|
|
||||||
whitelist.addAttributes("ol", "start", "type");
|
|
||||||
whitelist.addAttributes("q", "cite");
|
|
||||||
whitelist.addAttributes("table", "border", "bordercolor", "summary", "width");
|
|
||||||
whitelist.addAttributes("td", "border", "bordercolor", "abbr", "axis", "colspan", "rowspan", "width");
|
|
||||||
whitelist.addAttributes("th", "border", "bordercolor", "abbr", "axis", "colspan", "rowspan", "scope", "width");
|
|
||||||
whitelist.addAttributes("ul", "type");
|
|
||||||
|
|
||||||
whitelist.addProtocols("a", "href", "ftp", "http", "https", "mailto");
|
|
||||||
whitelist.addProtocols("blockquote", "cite", "http", "https");
|
|
||||||
whitelist.addProtocols("img", "src", "http", "https");
|
|
||||||
whitelist.addProtocols("q", "cite", "http", "https");
|
|
||||||
|
|
||||||
whitelist.addEnforcedAttribute("a", "target", "_blank");
|
|
||||||
|
|
||||||
Document dirty = Jsoup.parseBodyFragment(content, baseUri);
|
Document dirty = Jsoup.parseBodyFragment(content, baseUri);
|
||||||
Cleaner cleaner = new Cleaner(whitelist);
|
Cleaner cleaner = new Cleaner(WHITELIST);
|
||||||
Document clean = cleaner.clean(dirty);
|
Document clean = cleaner.clean(dirty);
|
||||||
|
|
||||||
for (Element e : clean.select("iframe[style]")) {
|
for (Element e : clean.select("iframe[style]")) {
|
||||||
@@ -389,17 +406,37 @@ public class FeedUtils {
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String toAbsoluteUrl(String url, String baseUrl) {
|
/**
|
||||||
|
*
|
||||||
|
* @param url
|
||||||
|
* the url of the entry
|
||||||
|
* @param feedLink
|
||||||
|
* the url of the feed as described in the feed
|
||||||
|
* @param feedUrl
|
||||||
|
* the url of the feed that we used to fetch the feed
|
||||||
|
* @return an absolute url pointing to the entry
|
||||||
|
*/
|
||||||
|
public static String toAbsoluteUrl(String url, String feedLink, String feedUrl) {
|
||||||
url = StringUtils.trimToNull(StringUtils.normalizeSpace(url));
|
url = StringUtils.trimToNull(StringUtils.normalizeSpace(url));
|
||||||
if (baseUrl == null || url == null || url.startsWith("http")) {
|
if (url == null || url.startsWith("http")) {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url.startsWith("/") == false) {
|
String baseUrl = (feedLink == null || UrlUtils.isRelative(feedLink)) ? feedUrl : feedLink;
|
||||||
url = "/" + url;
|
|
||||||
|
if (baseUrl == null) {
|
||||||
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
return baseUrl + url;
|
String result = null;
|
||||||
|
try {
|
||||||
|
result = new URL(new URL(baseUrl), url).toString();
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
log.debug("could not parse url : " + e.getMessage(), e);
|
||||||
|
result = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getFaviconUrl(FeedSubscription subscription, String publicUrl) {
|
public static String getFaviconUrl(FeedSubscription subscription, String publicUrl) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
266
src/main/java/com/commafeed/backend/feeds/HtmlEntities.java
Normal file
266
src/main/java/com/commafeed/backend/feeds/HtmlEntities.java
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
package com.commafeed.backend.feeds;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.google.gwt.thirdparty.guava.common.collect.Maps;
|
||||||
|
|
||||||
|
public class HtmlEntities {
|
||||||
|
public static final Map<String, String> NUMERIC_MAPPING = Collections.unmodifiableMap(loadMap());
|
||||||
|
|
||||||
|
private static synchronized Map<String, String> loadMap() {
|
||||||
|
Map<String, String> map = Maps.newLinkedHashMap();
|
||||||
|
map.put("Á", "Á");
|
||||||
|
map.put("á", "á");
|
||||||
|
map.put("Â", "Â");
|
||||||
|
map.put("â", "â");
|
||||||
|
map.put("´", "´");
|
||||||
|
map.put("Æ", "Æ");
|
||||||
|
map.put("æ", "æ");
|
||||||
|
map.put("À", "À");
|
||||||
|
map.put("à", "à");
|
||||||
|
map.put("ℵ", "ℵ");
|
||||||
|
map.put("Α", "Α");
|
||||||
|
map.put("α", "α");
|
||||||
|
map.put("&", "&");
|
||||||
|
map.put("∧", "∧");
|
||||||
|
map.put("∠", "∠");
|
||||||
|
map.put("Å", "Å");
|
||||||
|
map.put("å", "å");
|
||||||
|
map.put("≈", "≈");
|
||||||
|
map.put("Ã", "Ã");
|
||||||
|
map.put("ã", "ã");
|
||||||
|
map.put("Ä", "Ä");
|
||||||
|
map.put("ä", "ä");
|
||||||
|
map.put("„", "„");
|
||||||
|
map.put("Β", "Β");
|
||||||
|
map.put("β", "β");
|
||||||
|
map.put("¦", "¦");
|
||||||
|
map.put("•", "•");
|
||||||
|
map.put("∩", "∩");
|
||||||
|
map.put("Ç", "Ç");
|
||||||
|
map.put("ç", "ç");
|
||||||
|
map.put("¸", "¸");
|
||||||
|
map.put("¢", "¢");
|
||||||
|
map.put("Χ", "Χ");
|
||||||
|
map.put("χ", "χ");
|
||||||
|
map.put("ˆ", "ˆ");
|
||||||
|
map.put("♣", "♣");
|
||||||
|
map.put("≅", "≅");
|
||||||
|
map.put("©", "©");
|
||||||
|
map.put("↵", "↵");
|
||||||
|
map.put("∪", "∪");
|
||||||
|
map.put("¤", "¤");
|
||||||
|
map.put("†", "†");
|
||||||
|
map.put("‡", "‡");
|
||||||
|
map.put("↓", "↓");
|
||||||
|
map.put("⇓", "⇓");
|
||||||
|
map.put("°", "°");
|
||||||
|
map.put("Δ", "Δ");
|
||||||
|
map.put("δ", "δ");
|
||||||
|
map.put("♦", "♦");
|
||||||
|
map.put("÷", "÷");
|
||||||
|
map.put("É", "É");
|
||||||
|
map.put("é", "é");
|
||||||
|
map.put("Ê", "Ê");
|
||||||
|
map.put("ê", "ê");
|
||||||
|
map.put("È", "È");
|
||||||
|
map.put("è", "è");
|
||||||
|
map.put("∅", "∅");
|
||||||
|
map.put(" ", " ");
|
||||||
|
map.put(" ", " ");
|
||||||
|
map.put("Ε", "Ε");
|
||||||
|
map.put("ε", "ε");
|
||||||
|
map.put("≡", "≡");
|
||||||
|
map.put("Η", "Η");
|
||||||
|
map.put("η", "η");
|
||||||
|
map.put("Ð", "Ð");
|
||||||
|
map.put("ð", "ð");
|
||||||
|
map.put("Ë", "Ë");
|
||||||
|
map.put("ë", "ë");
|
||||||
|
map.put("€", "€");
|
||||||
|
map.put("∃", "∃");
|
||||||
|
map.put("ƒ", "ƒ");
|
||||||
|
map.put("∀", "∀");
|
||||||
|
map.put("½", "½");
|
||||||
|
map.put("¼", "¼");
|
||||||
|
map.put("¾", "¾");
|
||||||
|
map.put("⁄", "⁄");
|
||||||
|
map.put("Γ", "Γ");
|
||||||
|
map.put("γ", "γ");
|
||||||
|
map.put("≥", "≥");
|
||||||
|
map.put("↔", "↔");
|
||||||
|
map.put("⇔", "⇔");
|
||||||
|
map.put("♥", "♥");
|
||||||
|
map.put("…", "…");
|
||||||
|
map.put("Í", "Í");
|
||||||
|
map.put("í", "í");
|
||||||
|
map.put("Î", "Î");
|
||||||
|
map.put("î", "î");
|
||||||
|
map.put("¡", "¡");
|
||||||
|
map.put("Ì", "Ì");
|
||||||
|
map.put("ì", "ì");
|
||||||
|
map.put("ℑ", "ℑ");
|
||||||
|
map.put("∞", "∞");
|
||||||
|
map.put("∫", "∫");
|
||||||
|
map.put("Ι", "Ι");
|
||||||
|
map.put("ι", "ι");
|
||||||
|
map.put("¿", "¿");
|
||||||
|
map.put("∈", "∈");
|
||||||
|
map.put("Ï", "Ï");
|
||||||
|
map.put("ï", "ï");
|
||||||
|
map.put("Κ", "Κ");
|
||||||
|
map.put("κ", "κ");
|
||||||
|
map.put("Λ", "Λ");
|
||||||
|
map.put("λ", "λ");
|
||||||
|
map.put("⟨", "〈");
|
||||||
|
map.put("«", "«");
|
||||||
|
map.put("←", "←");
|
||||||
|
map.put("⇐", "⇐");
|
||||||
|
map.put("⌈", "⌈");
|
||||||
|
map.put("“", "“");
|
||||||
|
map.put("≤", "≤");
|
||||||
|
map.put("⌊", "⌊");
|
||||||
|
map.put("∗", "∗");
|
||||||
|
map.put("◊", "◊");
|
||||||
|
map.put("‎", "‎");
|
||||||
|
map.put("‹", "‹");
|
||||||
|
map.put("‘", "‘");
|
||||||
|
map.put("¯", "¯");
|
||||||
|
map.put("—", "—");
|
||||||
|
map.put("µ", "µ");
|
||||||
|
map.put("·", "·");
|
||||||
|
map.put("−", "−");
|
||||||
|
map.put("Μ", "Μ");
|
||||||
|
map.put("μ", "μ");
|
||||||
|
map.put("∇", "∇");
|
||||||
|
map.put(" ", " ");
|
||||||
|
map.put("–", "–");
|
||||||
|
map.put("≠", "≠");
|
||||||
|
map.put("∋", "∋");
|
||||||
|
map.put("¬", "¬");
|
||||||
|
map.put("∉", "∉");
|
||||||
|
map.put("⊄", "⊄");
|
||||||
|
map.put("Ñ", "Ñ");
|
||||||
|
map.put("ñ", "ñ");
|
||||||
|
map.put("Ν", "Ν");
|
||||||
|
map.put("ν", "ν");
|
||||||
|
map.put("Ó", "Ó");
|
||||||
|
map.put("ó", "ó");
|
||||||
|
map.put("Ô", "Ô");
|
||||||
|
map.put("ô", "ô");
|
||||||
|
map.put("Œ", "Œ");
|
||||||
|
map.put("œ", "œ");
|
||||||
|
map.put("Ò", "Ò");
|
||||||
|
map.put("ò", "ò");
|
||||||
|
map.put("‾", "‾");
|
||||||
|
map.put("Ω", "Ω");
|
||||||
|
map.put("ω", "ω");
|
||||||
|
map.put("Ο", "Ο");
|
||||||
|
map.put("ο", "ο");
|
||||||
|
map.put("⊕", "⊕");
|
||||||
|
map.put("∨", "∨");
|
||||||
|
map.put("ª", "ª");
|
||||||
|
map.put("º", "º");
|
||||||
|
map.put("Ø", "Ø");
|
||||||
|
map.put("ø", "ø");
|
||||||
|
map.put("Õ", "Õ");
|
||||||
|
map.put("õ", "õ");
|
||||||
|
map.put("⊗", "⊗");
|
||||||
|
map.put("Ö", "Ö");
|
||||||
|
map.put("ö", "ö");
|
||||||
|
map.put("¶", "¶");
|
||||||
|
map.put("∂", "∂");
|
||||||
|
map.put("‰", "‰");
|
||||||
|
map.put("⊥", "⊥");
|
||||||
|
map.put("Φ", "Φ");
|
||||||
|
map.put("φ", "φ");
|
||||||
|
map.put("Π", "Π");
|
||||||
|
map.put("π", "π");
|
||||||
|
map.put("ϖ", "ϖ");
|
||||||
|
map.put("±", "±");
|
||||||
|
map.put("£", "£");
|
||||||
|
map.put("′", "′");
|
||||||
|
map.put("″", "″");
|
||||||
|
map.put("∏", "∏");
|
||||||
|
map.put("∝", "∝");
|
||||||
|
map.put("Ψ", "Ψ");
|
||||||
|
map.put("ψ", "ψ");
|
||||||
|
map.put(""", """);
|
||||||
|
map.put("√", "√");
|
||||||
|
map.put("⟩", "〉");
|
||||||
|
map.put("»", "»");
|
||||||
|
map.put("→", "→");
|
||||||
|
map.put("⇒", "⇒");
|
||||||
|
map.put("⌉", "⌉");
|
||||||
|
map.put("”", "”");
|
||||||
|
map.put("ℜ", "ℜ");
|
||||||
|
map.put("®", "®");
|
||||||
|
map.put("⌋", "⌋");
|
||||||
|
map.put("Ρ", "Ρ");
|
||||||
|
map.put("ρ", "ρ");
|
||||||
|
map.put("‏", "‏");
|
||||||
|
map.put("›", "›");
|
||||||
|
map.put("’", "’");
|
||||||
|
map.put("‚", "‚");
|
||||||
|
map.put("Š", "Š");
|
||||||
|
map.put("š", "š");
|
||||||
|
map.put("⋅", "⋅");
|
||||||
|
map.put("§", "§");
|
||||||
|
map.put("­", "­");
|
||||||
|
map.put("Σ", "Σ");
|
||||||
|
map.put("σ", "σ");
|
||||||
|
map.put("ς", "ς");
|
||||||
|
map.put("∼", "∼");
|
||||||
|
map.put("♠", "♠");
|
||||||
|
map.put("⊂", "⊂");
|
||||||
|
map.put("⊆", "⊆");
|
||||||
|
map.put("∑", "∑");
|
||||||
|
map.put("¹", "¹");
|
||||||
|
map.put("²", "²");
|
||||||
|
map.put("³", "³");
|
||||||
|
map.put("⊃", "⊃");
|
||||||
|
map.put("⊇", "⊇");
|
||||||
|
map.put("ß", "ß");
|
||||||
|
map.put("Τ", "Τ");
|
||||||
|
map.put("τ", "τ");
|
||||||
|
map.put("∴", "∴");
|
||||||
|
map.put("Θ", "Θ");
|
||||||
|
map.put("θ", "θ");
|
||||||
|
map.put("ϑ", "ϑ");
|
||||||
|
map.put(" ", " ");
|
||||||
|
map.put("Þ", "Þ");
|
||||||
|
map.put("þ", "þ");
|
||||||
|
map.put("˜", "˜");
|
||||||
|
map.put("×", "×");
|
||||||
|
map.put("™", "™");
|
||||||
|
map.put("Ú", "Ú");
|
||||||
|
map.put("ú", "ú");
|
||||||
|
map.put("↑", "↑");
|
||||||
|
map.put("⇑", "⇑");
|
||||||
|
map.put("Û", "Û");
|
||||||
|
map.put("û", "û");
|
||||||
|
map.put("Ù", "Ù");
|
||||||
|
map.put("ù", "ù");
|
||||||
|
map.put("¨", "¨");
|
||||||
|
map.put("ϒ", "ϒ");
|
||||||
|
map.put("Υ", "Υ");
|
||||||
|
map.put("υ", "υ");
|
||||||
|
map.put("Ü", "Ü");
|
||||||
|
map.put("ü", "ü");
|
||||||
|
map.put("℘", "℘");
|
||||||
|
map.put("Ξ", "Ξ");
|
||||||
|
map.put("ξ", "ξ");
|
||||||
|
map.put("Ý", "Ý");
|
||||||
|
map.put("ý", "ý");
|
||||||
|
map.put("¥", "¥");
|
||||||
|
map.put("ÿ", "ÿ");
|
||||||
|
map.put("Ÿ", "Ÿ");
|
||||||
|
map.put("Ζ", "Ζ");
|
||||||
|
map.put("ζ", "ζ");
|
||||||
|
map.put("‍", "‍");
|
||||||
|
map.put("‌", "‌");
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,5 +55,8 @@ public class FeedEntry extends AbstractModel {
|
|||||||
|
|
||||||
@OneToMany(mappedBy = "entry", cascade = CascadeType.REMOVE)
|
@OneToMany(mappedBy = "entry", cascade = CascadeType.REMOVE)
|
||||||
private Set<FeedEntryStatus> statuses;
|
private Set<FeedEntryStatus> statuses;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "entry", cascade = CascadeType.REMOVE)
|
||||||
|
private Set<FeedEntryTag> tags;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.commafeed.backend.model;
|
package com.commafeed.backend.model;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.persistence.Cacheable;
|
import javax.persistence.Cacheable;
|
||||||
import javax.persistence.Column;
|
import javax.persistence.Column;
|
||||||
@@ -8,6 +9,8 @@ import javax.persistence.Entity;
|
|||||||
import javax.persistence.FetchType;
|
import javax.persistence.FetchType;
|
||||||
import javax.persistence.JoinColumn;
|
import javax.persistence.JoinColumn;
|
||||||
import javax.persistence.ManyToOne;
|
import javax.persistence.ManyToOne;
|
||||||
|
import javax.persistence.NamedQueries;
|
||||||
|
import javax.persistence.NamedQuery;
|
||||||
import javax.persistence.Table;
|
import javax.persistence.Table;
|
||||||
import javax.persistence.Temporal;
|
import javax.persistence.Temporal;
|
||||||
import javax.persistence.TemporalType;
|
import javax.persistence.TemporalType;
|
||||||
@@ -19,6 +22,8 @@ import lombok.Setter;
|
|||||||
import org.hibernate.annotations.Cache;
|
import org.hibernate.annotations.Cache;
|
||||||
import org.hibernate.annotations.CacheConcurrencyStrategy;
|
import org.hibernate.annotations.CacheConcurrencyStrategy;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "FEEDENTRYSTATUSES")
|
@Table(name = "FEEDENTRYSTATUSES")
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
@@ -26,6 +31,7 @@ import org.hibernate.annotations.CacheConcurrencyStrategy;
|
|||||||
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
|
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
|
@NamedQueries(@NamedQuery(name="Statuses.deleteOld", query="delete from FeedEntryStatus s where s.entryInserted < :date and s.starred = false"))
|
||||||
public class FeedEntryStatus extends AbstractModel {
|
public class FeedEntryStatus extends AbstractModel {
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
@@ -42,6 +48,9 @@ public class FeedEntryStatus extends AbstractModel {
|
|||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
private boolean markable;
|
private boolean markable;
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
private List<FeedEntryTag> tags = Lists.newArrayList();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Denormalization starts here
|
* Denormalization starts here
|
||||||
|
|||||||
46
src/main/java/com/commafeed/backend/model/FeedEntryTag.java
Normal file
46
src/main/java/com/commafeed/backend/model/FeedEntryTag.java
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package com.commafeed.backend.model;
|
||||||
|
|
||||||
|
import javax.persistence.Cacheable;
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.FetchType;
|
||||||
|
import javax.persistence.JoinColumn;
|
||||||
|
import javax.persistence.ManyToOne;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.Cache;
|
||||||
|
import org.hibernate.annotations.CacheConcurrencyStrategy;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "FEEDENTRYTAGS")
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
@Cacheable
|
||||||
|
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class FeedEntryTag extends AbstractModel {
|
||||||
|
|
||||||
|
@JoinColumn(name = "user_id")
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
private User user;
|
||||||
|
|
||||||
|
@JoinColumn(name = "entry_id")
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
private FeedEntry entry;
|
||||||
|
|
||||||
|
@Column(name = "name", length = 40)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public FeedEntryTag() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public FeedEntryTag(User user, FeedEntry entry, String name) {
|
||||||
|
this.name = name;
|
||||||
|
this.entry = entry;
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -32,7 +32,7 @@ public class User extends AbstractModel {
|
|||||||
|
|
||||||
@Column(length = 32, nullable = false, unique = true)
|
@Column(length = 32, nullable = false, unique = true)
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
@Column(length = 255, unique = true)
|
@Column(length = 255, unique = true)
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
@@ -66,4 +66,8 @@ public class User extends AbstractModel {
|
|||||||
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
|
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
|
||||||
private Set<FeedSubscription> subscriptions;
|
private Set<FeedSubscription> subscriptions;
|
||||||
|
|
||||||
|
@Column(name = "last_full_refresh")
|
||||||
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
|
private Date lastFullRefresh;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ public class UserSettings extends AbstractModel {
|
|||||||
|
|
||||||
private boolean showRead;
|
private boolean showRead;
|
||||||
private boolean scrollMarks;
|
private boolean scrollMarks;
|
||||||
private boolean socialButtons;
|
|
||||||
|
|
||||||
@Column(length = 32)
|
@Column(length = 32)
|
||||||
private String theme;
|
private String theme;
|
||||||
@@ -68,4 +67,18 @@ public class UserSettings extends AbstractModel {
|
|||||||
@Column(length = Integer.MAX_VALUE)
|
@Column(length = Integer.MAX_VALUE)
|
||||||
private String customCss;
|
private String customCss;
|
||||||
|
|
||||||
|
@Column(name = "scroll_speed")
|
||||||
|
private int scrollSpeed;
|
||||||
|
|
||||||
|
private boolean email;
|
||||||
|
private boolean gmail;
|
||||||
|
private boolean facebook;
|
||||||
|
private boolean twitter;
|
||||||
|
private boolean googleplus;
|
||||||
|
private boolean tumblr;
|
||||||
|
private boolean pocket;
|
||||||
|
private boolean instapaper;
|
||||||
|
private boolean buffer;
|
||||||
|
private boolean readability;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -34,9 +34,14 @@ public class OPMLExporter {
|
|||||||
List<FeedCategory> categories = feedCategoryDAO.findAll(user);
|
List<FeedCategory> categories = feedCategoryDAO.findAll(user);
|
||||||
List<FeedSubscription> subscriptions = feedSubscriptionDAO.findAll(user);
|
List<FeedSubscription> subscriptions = feedSubscriptionDAO.findAll(user);
|
||||||
|
|
||||||
|
// export root categories
|
||||||
for (FeedCategory cat : categories) {
|
for (FeedCategory cat : categories) {
|
||||||
opml.getOutlines().add(buildCategoryOutline(cat, subscriptions));
|
if (cat.getParent() == null) {
|
||||||
|
opml.getOutlines().add(buildCategoryOutline(cat, subscriptions));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// export root subscriptions
|
||||||
for (FeedSubscription sub : subscriptions) {
|
for (FeedSubscription sub : subscriptions) {
|
||||||
if (sub.getCategory() == null) {
|
if (sub.getCategory() == null) {
|
||||||
opml.getOutlines().add(buildSubscriptionOutline(sub));
|
opml.getOutlines().add(buildSubscriptionOutline(sub));
|
||||||
@@ -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) {
|
||||||
@@ -9,13 +9,14 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.http.HttpHeaders;
|
import org.apache.http.HttpHeaders;
|
||||||
import org.apache.http.HttpResponse;
|
|
||||||
import org.apache.http.NameValuePair;
|
import org.apache.http.NameValuePair;
|
||||||
import org.apache.http.client.HttpClient;
|
|
||||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
import org.apache.http.client.methods.HttpPost;
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
import org.apache.http.message.BasicNameValuePair;
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
import org.apache.http.util.EntityUtils;
|
import org.apache.http.util.EntityUtils;
|
||||||
|
import org.apache.wicket.util.io.IOUtils;
|
||||||
|
|
||||||
import com.commafeed.backend.HttpGetter;
|
import com.commafeed.backend.HttpGetter;
|
||||||
import com.commafeed.backend.feeds.FeedRefreshTaskGiver;
|
import com.commafeed.backend.feeds.FeedRefreshTaskGiver;
|
||||||
@@ -67,10 +68,11 @@ public class SubscriptionHandler {
|
|||||||
post.setHeader(HttpHeaders.USER_AGENT, "CommaFeed");
|
post.setHeader(HttpHeaders.USER_AGENT, "CommaFeed");
|
||||||
post.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED);
|
post.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
|
|
||||||
HttpClient client = HttpGetter.newClient(20000);
|
CloseableHttpClient client = HttpGetter.newClient(20000);
|
||||||
|
CloseableHttpResponse response = null;
|
||||||
try {
|
try {
|
||||||
post.setEntity(new UrlEncodedFormEntity(nvp));
|
post.setEntity(new UrlEncodedFormEntity(nvp));
|
||||||
HttpResponse response = client.execute(post);
|
response = client.execute(post);
|
||||||
|
|
||||||
int code = response.getStatusLine().getStatusCode();
|
int code = response.getStatusLine().getStatusCode();
|
||||||
if (code != 204 && code != 202 && code != 200) {
|
if (code != 204 && code != 202 && code != 200) {
|
||||||
@@ -90,7 +92,8 @@ public class SubscriptionHandler {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Could not subscribe to {} for {} : " + e.getMessage(), hub, topic);
|
log.error("Could not subscribe to {} for {} : " + e.getMessage(), hub, topic);
|
||||||
} finally {
|
} finally {
|
||||||
client.getConnectionManager().shutdown();
|
IOUtils.closeQuietly(response);
|
||||||
|
IOUtils.closeQuietly(client);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,18 @@ 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.FeedEntry;
|
||||||
|
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 {
|
||||||
|
|
||||||
|
private static final int BATCH_SIZE = 100;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FeedDAO feedDAO;
|
FeedDAO feedDAO;
|
||||||
@@ -42,13 +46,28 @@ public class DatabaseCleaner {
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ApplicationSettingsService applicationSettingsService;
|
ApplicationSettingsService applicationSettingsService;
|
||||||
|
|
||||||
|
public long cleanEntriesWithoutSubscriptions() {
|
||||||
|
log.info("cleaning entries without subscriptions");
|
||||||
|
long total = 0;
|
||||||
|
int deleted = 0;
|
||||||
|
do {
|
||||||
|
List<FeedEntry> entries = feedEntryDAO.findWithoutSubscriptions(BATCH_SIZE);
|
||||||
|
deleted = feedEntryDAO.delete(entries);
|
||||||
|
total += deleted;
|
||||||
|
log.info("removed {} entries without subscriptions", total);
|
||||||
|
} while (deleted != 0);
|
||||||
|
log.info("cleanup done: {} entries without subscriptions deleted", total);
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
public long cleanFeedsWithoutSubscriptions() {
|
public long cleanFeedsWithoutSubscriptions() {
|
||||||
|
log.info("cleaning feeds without subscriptions");
|
||||||
long total = 0;
|
long total = 0;
|
||||||
int deleted = -1;
|
int deleted = 0;
|
||||||
do {
|
do {
|
||||||
deleted = feedDAO.deleteWithoutSubscriptions(10);
|
List<Feed> feeds = feedDAO.findWithoutSubscriptions(BATCH_SIZE);
|
||||||
|
deleted = feedDAO.delete(feeds);
|
||||||
total += deleted;
|
total += deleted;
|
||||||
log.info("removed {} feeds without subscriptions", total);
|
log.info("removed {} feeds without subscriptions", total);
|
||||||
} while (deleted != 0);
|
} while (deleted != 0);
|
||||||
@@ -57,15 +76,15 @@ public class DatabaseCleaner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public long cleanContentsWithoutEntries() {
|
public long cleanContentsWithoutEntries() {
|
||||||
|
log.info("cleaning contents without entries");
|
||||||
long total = 0;
|
long total = 0;
|
||||||
int deleted = -1;
|
int deleted = 0;
|
||||||
do {
|
do {
|
||||||
deleted = feedEntryContentDAO.deleteWithoutEntries(10);
|
deleted = feedEntryContentDAO.deleteWithoutEntries(BATCH_SIZE);
|
||||||
total += deleted;
|
total += deleted;
|
||||||
log.info("removed {} feeds without subscriptions", total);
|
log.info("removed {} contents without entries", total);
|
||||||
} while (deleted != 0);
|
} while (deleted != 0);
|
||||||
log.info("cleanup done: {} feeds without subscriptions deleted", total);
|
log.info("cleanup done: {} contents without entries deleted", total);
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,9 +93,9 @@ public class DatabaseCleaner {
|
|||||||
cal.add(Calendar.MINUTE, -1 * (int) unit.toMinutes(value));
|
cal.add(Calendar.MINUTE, -1 * (int) unit.toMinutes(value));
|
||||||
|
|
||||||
long total = 0;
|
long total = 0;
|
||||||
int deleted = -1;
|
int deleted = 0;
|
||||||
do {
|
do {
|
||||||
deleted = feedEntryDAO.delete(cal.getTime(), 100);
|
deleted = feedEntryDAO.delete(cal.getTime(), BATCH_SIZE);
|
||||||
total += deleted;
|
total += deleted;
|
||||||
log.info("removed {} entries", total);
|
log.info("removed {} entries", total);
|
||||||
} while (deleted != 0);
|
} while (deleted != 0);
|
||||||
@@ -99,9 +118,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, BATCH_SIZE);
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ public class FeedEntryService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FeedEntryStatus status = feedEntryStatusDAO.getStatus(sub, entry);
|
FeedEntryStatus status = feedEntryStatusDAO.getStatus(user, sub, entry);
|
||||||
if (status.isMarkable()) {
|
if (status.isMarkable()) {
|
||||||
status.setRead(read);
|
status.setRead(read);
|
||||||
feedEntryStatusDAO.saveOrUpdate(status);
|
feedEntryStatusDAO.saveOrUpdate(status);
|
||||||
@@ -64,14 +64,14 @@ public class FeedEntryService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FeedEntryStatus status = feedEntryStatusDAO.getStatus(sub, entry);
|
FeedEntryStatus status = feedEntryStatusDAO.getStatus(user, sub, entry);
|
||||||
status.setStarred(starred);
|
status.setStarred(starred);
|
||||||
feedEntryStatusDAO.saveOrUpdate(status);
|
feedEntryStatusDAO.saveOrUpdate(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void markSubscriptionEntries(User user, List<FeedSubscription> subscriptions, Date olderThan) {
|
public void markSubscriptionEntries(User user, List<FeedSubscription> subscriptions, Date olderThan) {
|
||||||
List<FeedEntryStatus> statuses = feedEntryStatusDAO
|
List<FeedEntryStatus> statuses = feedEntryStatusDAO.findBySubscriptions(user, subscriptions, true, null, null, -1, -1, null, false,
|
||||||
.findBySubscriptions(subscriptions, true, null, null, -1, -1, null, false, false);
|
false, null);
|
||||||
markList(statuses, olderThan);
|
markList(statuses, olderThan);
|
||||||
cache.invalidateUnreadCount(subscriptions.toArray(new FeedSubscription[0]));
|
cache.invalidateUnreadCount(subscriptions.toArray(new FeedSubscription[0]));
|
||||||
cache.invalidateUserRootCategory(user);
|
cache.invalidateUserRootCategory(user);
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package com.commafeed.backend.services;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.ejb.Stateless;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import com.commafeed.backend.dao.FeedEntryDAO;
|
||||||
|
import com.commafeed.backend.dao.FeedEntryTagDAO;
|
||||||
|
import com.commafeed.backend.model.FeedEntry;
|
||||||
|
import com.commafeed.backend.model.FeedEntryTag;
|
||||||
|
import com.commafeed.backend.model.User;
|
||||||
|
import com.google.common.base.Function;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
|
@Stateless
|
||||||
|
public class FeedEntryTagService {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FeedEntryDAO feedEntryDAO;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FeedEntryTagDAO feedEntryTagDAO;
|
||||||
|
|
||||||
|
public void updateTags(User user, Long entryId, List<String> tagNames) {
|
||||||
|
FeedEntry entry = feedEntryDAO.findById(entryId);
|
||||||
|
if (entry == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<FeedEntryTag> tags = feedEntryTagDAO.findByEntry(user, entry);
|
||||||
|
Map<String, FeedEntryTag> tagMap = Maps.uniqueIndex(tags, new Function<FeedEntryTag, String>() {
|
||||||
|
@Override
|
||||||
|
public String apply(FeedEntryTag input) {
|
||||||
|
return input.getName();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
List<FeedEntryTag> addList = Lists.newArrayList();
|
||||||
|
List<FeedEntryTag> removeList = Lists.newArrayList();
|
||||||
|
|
||||||
|
for (String tagName : tagNames) {
|
||||||
|
FeedEntryTag tag = tagMap.get(tagName);
|
||||||
|
if (tag == null) {
|
||||||
|
addList.add(new FeedEntryTag(user, entry, tagName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (FeedEntryTag tag : tags) {
|
||||||
|
if (!tagNames.contains(tag.getName())) {
|
||||||
|
removeList.add(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
feedEntryTagDAO.saveOrUpdate(addList);
|
||||||
|
feedEntryTagDAO.delete(removeList);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -95,11 +95,19 @@ public class FeedSubscriptionService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public UnreadCount getUnreadCount(FeedSubscription sub) {
|
public void refreshAll(User user) {
|
||||||
|
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user);
|
||||||
|
for (FeedSubscription sub : subs) {
|
||||||
|
Feed feed = sub.getFeed();
|
||||||
|
taskGiver.add(feed, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnreadCount getUnreadCount(User user, FeedSubscription sub) {
|
||||||
UnreadCount count = cache.getUnreadCount(sub);
|
UnreadCount count = cache.getUnreadCount(sub);
|
||||||
if (count == null) {
|
if (count == null) {
|
||||||
log.debug("unread count cache miss for {}", Models.getId(sub));
|
log.debug("unread count cache miss for {}", Models.getId(sub));
|
||||||
count = feedEntryStatusDAO.getUnreadCount(sub);
|
count = feedEntryStatusDAO.getUnreadCount(user, sub);
|
||||||
cache.setUnreadCount(sub, count);
|
cache.setUnreadCount(sub, count);
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
@@ -109,7 +117,7 @@ public class FeedSubscriptionService {
|
|||||||
Map<Long, UnreadCount> map = Maps.newHashMap();
|
Map<Long, UnreadCount> map = Maps.newHashMap();
|
||||||
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user);
|
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user);
|
||||||
for (FeedSubscription sub : subs) {
|
for (FeedSubscription sub : subs) {
|
||||||
map.put(sub.getId(), getUnreadCount(sub));
|
map.put(sub.getId(), getUnreadCount(user, sub));
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ public class UserService {
|
|||||||
@Inject
|
@Inject
|
||||||
ApplicationSettingsService applicationSettingsService;
|
ApplicationSettingsService applicationSettingsService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FeedSubscriptionService feedSubscriptionService;
|
||||||
|
|
||||||
public User login(String name, String password) {
|
public User login(String name, String password) {
|
||||||
if (name == null || password == null) {
|
if (name == null || password == null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -52,10 +55,21 @@ public class UserService {
|
|||||||
if (authenticated) {
|
if (authenticated) {
|
||||||
Date lastLogin = user.getLastLogin();
|
Date lastLogin = user.getLastLogin();
|
||||||
Date now = new Date();
|
Date now = new Date();
|
||||||
|
|
||||||
|
boolean saveUser = false;
|
||||||
// only update lastLogin field every hour in order to not
|
// only update lastLogin field every hour in order to not
|
||||||
// invalidate the cache everytime someone logs in
|
// invalidate the cache everytime someone logs in
|
||||||
if (lastLogin == null || lastLogin.before(DateUtils.addHours(now, -1))) {
|
if (lastLogin == null || lastLogin.before(DateUtils.addHours(now, -1))) {
|
||||||
user.setLastLogin(now);
|
user.setLastLogin(now);
|
||||||
|
saveUser = true;
|
||||||
|
}
|
||||||
|
if (applicationSettingsService.get().isHeavyLoad()
|
||||||
|
&& (user.getLastFullRefresh() == null || user.getLastFullRefresh().before(DateUtils.addMinutes(now, -30)))) {
|
||||||
|
user.setLastFullRefresh(now);
|
||||||
|
saveUser = true;
|
||||||
|
feedSubscriptionService.refreshAll(user);
|
||||||
|
}
|
||||||
|
if (saveUser) {
|
||||||
userDAO.saveOrUpdate(user);
|
userDAO.saveOrUpdate(user);
|
||||||
}
|
}
|
||||||
return user;
|
return user;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -41,4 +41,7 @@ public class Entries implements Serializable {
|
|||||||
@ApiProperty("list of entries")
|
@ApiProperty("list of entries")
|
||||||
private List<Entry> entries = Lists.newArrayList();
|
private List<Entry> entries = Lists.newArrayList();
|
||||||
|
|
||||||
|
@ApiProperty("if true, the unread flag was ignored in the request, all entries are returned regardless of their read status")
|
||||||
|
private boolean ignoredReadStatus;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.commafeed.frontend.model;
|
|||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@@ -10,7 +11,9 @@ import com.commafeed.backend.feeds.FeedUtils;
|
|||||||
import com.commafeed.backend.model.FeedEntry;
|
import com.commafeed.backend.model.FeedEntry;
|
||||||
import com.commafeed.backend.model.FeedEntryContent;
|
import com.commafeed.backend.model.FeedEntryContent;
|
||||||
import com.commafeed.backend.model.FeedEntryStatus;
|
import com.commafeed.backend.model.FeedEntryStatus;
|
||||||
|
import com.commafeed.backend.model.FeedEntryTag;
|
||||||
import com.commafeed.backend.model.FeedSubscription;
|
import com.commafeed.backend.model.FeedSubscription;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
import com.sun.syndication.feed.synd.SyndContentImpl;
|
import com.sun.syndication.feed.synd.SyndContentImpl;
|
||||||
import com.sun.syndication.feed.synd.SyndEntry;
|
import com.sun.syndication.feed.synd.SyndEntry;
|
||||||
import com.sun.syndication.feed.synd.SyndEntryImpl;
|
import com.sun.syndication.feed.synd.SyndEntryImpl;
|
||||||
@@ -43,6 +46,12 @@ public class Entry implements Serializable {
|
|||||||
entry.setFeedLink(sub.getFeed().getLink());
|
entry.setFeedLink(sub.getFeed().getLink());
|
||||||
entry.setIconUrl(FeedUtils.getFaviconUrl(sub, publicUrl));
|
entry.setIconUrl(FeedUtils.getFaviconUrl(sub, publicUrl));
|
||||||
|
|
||||||
|
List<String> tags = Lists.newArrayList();
|
||||||
|
for (FeedEntryTag tag : status.getTags()) {
|
||||||
|
tags.add(tag.getName());
|
||||||
|
}
|
||||||
|
entry.setTags(tags);
|
||||||
|
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
entry.setRtl(FeedUtils.isRTL(feedEntry));
|
entry.setRtl(FeedUtils.isRTL(feedEntry));
|
||||||
entry.setTitle(content.getTitle());
|
entry.setTitle(content.getTitle());
|
||||||
@@ -125,4 +134,7 @@ public class Entry implements Serializable {
|
|||||||
|
|
||||||
@ApiProperty("wether the entry is still markable (old entry statuses are discarded)")
|
@ApiProperty("wether the entry is still markable (old entry statuses are discarded)")
|
||||||
private boolean markable;
|
private boolean markable;
|
||||||
|
|
||||||
|
@ApiProperty("tags")
|
||||||
|
private List<String> tags;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,9 +27,6 @@ public class Settings implements Serializable {
|
|||||||
@ApiProperty(value = "user wants category and feeds with no unread entries shown", required = true)
|
@ApiProperty(value = "user wants category and feeds with no unread entries shown", required = true)
|
||||||
private boolean showRead;
|
private boolean showRead;
|
||||||
|
|
||||||
@ApiProperty(value = "user wants social buttons (facebook, twitter, ...) shown", required = true)
|
|
||||||
private boolean socialButtons;
|
|
||||||
|
|
||||||
@ApiProperty(value = "In expanded view, scroll through entries mark them as read", required = true)
|
@ApiProperty(value = "In expanded view, scroll through entries mark them as read", required = true)
|
||||||
private boolean scrollMarks;
|
private boolean scrollMarks;
|
||||||
|
|
||||||
@@ -38,5 +35,19 @@ public class Settings implements Serializable {
|
|||||||
|
|
||||||
@ApiProperty(value = "user's custom css for the website")
|
@ApiProperty(value = "user's custom css for the website")
|
||||||
private String customCss;
|
private String customCss;
|
||||||
|
|
||||||
|
@ApiProperty(value = "user's preferred scroll speed when navigating between entries")
|
||||||
|
private int scrollSpeed;
|
||||||
|
|
||||||
|
private boolean email;
|
||||||
|
private boolean gmail;
|
||||||
|
private boolean facebook;
|
||||||
|
private boolean twitter;
|
||||||
|
private boolean googleplus;
|
||||||
|
private boolean tumblr;
|
||||||
|
private boolean pocket;
|
||||||
|
private boolean instapaper;
|
||||||
|
private boolean buffer;
|
||||||
|
private boolean readability;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.commafeed.frontend.model.request;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import com.wordnik.swagger.annotations.ApiClass;
|
||||||
|
import com.wordnik.swagger.annotations.ApiProperty;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
@ApiClass("Tag Request")
|
||||||
|
@Data
|
||||||
|
public class TagRequest implements Serializable {
|
||||||
|
|
||||||
|
@ApiProperty(value = "entry id", required = true)
|
||||||
|
private Long entryId;
|
||||||
|
|
||||||
|
@ApiProperty(value = "tags")
|
||||||
|
private List<String> tags;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,45 +1,54 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html xmlns:wicket="http://wicket.apache.org" wicket:id="html">
|
<html xmlns:wicket="http://wicket.apache.org" wicket:id="html">
|
||||||
<head>
|
<head>
|
||||||
<title>CommaFeed</title>
|
<title>CommaFeed</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
|
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
|
||||||
<link rel="apple-touch-icon" href="app-icon-57.png" />
|
<link rel="apple-touch-icon" href="app-icon-57.png" />
|
||||||
<link rel="apple-touch-icon" sizes="72x72" href="app-icon-72.png" />
|
<link rel="apple-touch-icon" sizes="72x72" href="app-icon-72.png" />
|
||||||
<link rel="apple-touch-icon" sizes="114x114" href="app-icon-114.png" />
|
<link rel="apple-touch-icon" sizes="114x114" href="app-icon-114.png" />
|
||||||
<link rel="apple-touch-icon" sizes="144x144" href="app-icon-144.png" />
|
<link rel="apple-touch-icon" sizes="144x144" href="app-icon-144.png" />
|
||||||
<link rel="icon" sizes="32x32" href="app-icon-32.png" />
|
<link rel="icon" sizes="32x32" href="app-icon-32.png" />
|
||||||
<link rel="icon" sizes="64x64" href="app-icon-64.png" />
|
<link rel="icon" sizes="64x64" href="app-icon-64.png" />
|
||||||
<link rel="icon" sizes="128x128" href="app-icon-128.png" />
|
<link rel="icon" sizes="128x128" href="app-icon-128.png" />
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
|
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
|
||||||
<meta name="application-name" content="CommaFeed" />
|
<meta name="application-name" content="CommaFeed" />
|
||||||
<meta name="msapplication-navbutton-color" content="#F88A14" />
|
<meta name="msapplication-navbutton-color" content="#F88A14" />
|
||||||
<meta name="msapplication-starturl" content="/" />
|
<meta name="msapplication-starturl" content="/" />
|
||||||
<meta name="msapplication-square70x70logo" content="metro-icon-70.png"/>
|
<meta name="msapplication-square70x70logo" content="metro-icon-70.png" />
|
||||||
<meta name="msapplication-square150x150logo" content="metro-icon-150.png"/>
|
<meta name="msapplication-square150x150logo" content="metro-icon-150.png" />
|
||||||
<link rel="fluid-icon" href="app-icon-512.png" title="CommaFeed" />
|
<link rel="fluid-icon" href="app-icon-512.png" title="CommaFeed" />
|
||||||
<link rel="logo" type="image/svg" href="app-icon.svg" />
|
<link rel="logo" type="image/svg" href="app-icon.svg" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<wicket:child />
|
<wicket:child />
|
||||||
<wicket:container wicket:id="footer-container"/>
|
<wicket:container wicket:id="footer-container" />
|
||||||
<wicket:container wicket:id="uservoice">
|
<wicket:container wicket:id="uservoice">
|
||||||
<script>(function(){var uv=document.createElement('script');uv.type='text/javascript';uv.async=true;uv.src='//widget.uservoice.com/XYpTZZteqS4lHvgrTXeA.js';var s=document.getElementsByTagName('script')[0];s.parentNode.insertBefore(uv,s)})()</script>
|
<script>
|
||||||
|
(function() {
|
||||||
|
var uv = document.createElement('script');
|
||||||
|
uv.type = 'text/javascript';
|
||||||
|
uv.async = true;
|
||||||
|
uv.src = '//widget.uservoice.com/XYpTZZteqS4lHvgrTXeA.js';
|
||||||
|
var s = document.getElementsByTagName('script')[0];
|
||||||
|
s.parentNode.insertBefore(uv, s);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
<script>
|
<script>
|
||||||
UserVoice = window.UserVoice || [];
|
UserVoice = window.UserVoice || [];
|
||||||
UserVoice.push(['showTab', 'classic_widget', {
|
UserVoice.push(['showTab', 'classic_widget', {
|
||||||
mode: 'full',
|
mode : 'full',
|
||||||
default_mode: 'feedback',
|
default_mode : 'feedback',
|
||||||
primary_color: '#000',
|
primary_color : '#000',
|
||||||
link_color: '#007dbf',
|
link_color : '#007dbf',
|
||||||
forum_id: 204509,
|
forum_id : 204509,
|
||||||
support_tab_name: 'Contact',
|
support_tab_name : 'Contact',
|
||||||
feedback_tab_name: 'Feedback',
|
feedback_tab_name : 'Feedback',
|
||||||
tab_label: 'Feedback',
|
tab_label : 'Feedback',
|
||||||
tab_color: '#7e72db',
|
tab_color : '#7e72db',
|
||||||
tab_position: 'bottom-right',
|
tab_position : 'bottom-right',
|
||||||
tab_inverted: false
|
tab_inverted : false
|
||||||
}]);
|
}]);
|
||||||
</script>
|
</script>
|
||||||
</wicket:container>
|
</wicket:container>
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -54,13 +54,13 @@ public class NextUnreadRedirectPage extends WebPage {
|
|||||||
List<FeedEntryStatus> statuses = null;
|
List<FeedEntryStatus> statuses = null;
|
||||||
if (StringUtils.isBlank(categoryId) || CategoryREST.ALL.equals(categoryId)) {
|
if (StringUtils.isBlank(categoryId) || CategoryREST.ALL.equals(categoryId)) {
|
||||||
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user);
|
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user);
|
||||||
statuses = feedEntryStatusDAO.findBySubscriptions(subs, true, null, null, 0, 1, order, true, false);
|
statuses = feedEntryStatusDAO.findBySubscriptions(user, subs, true, null, null, 0, 1, order, true, false, null);
|
||||||
} else {
|
} else {
|
||||||
FeedCategory category = feedCategoryDAO.findById(user, Long.valueOf(categoryId));
|
FeedCategory category = feedCategoryDAO.findById(user, Long.valueOf(categoryId));
|
||||||
if (category != null) {
|
if (category != null) {
|
||||||
List<FeedCategory> children = feedCategoryDAO.findAllChildrenCategories(user, category);
|
List<FeedCategory> children = feedCategoryDAO.findAllChildrenCategories(user, category);
|
||||||
List<FeedSubscription> subscriptions = feedSubscriptionDAO.findByCategories(user, children);
|
List<FeedSubscription> subscriptions = feedSubscriptionDAO.findByCategories(user, children);
|
||||||
statuses = feedEntryStatusDAO.findBySubscriptions(subscriptions, true, null, null, 0, 1, order, true, false);
|
statuses = feedEntryStatusDAO.findBySubscriptions(user, subscriptions, true, null, null, 0, 1, order, true, false, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,15 +5,19 @@
|
|||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<img src="images/logo_2.png" />
|
<img src="images/logo_2.png" />
|
||||||
<div wicket:id="feedback"></div>
|
<div wicket:id="feedback"></div>
|
||||||
<form wicket:id="form">
|
<form wicket:id="form" class="form-horizontal">
|
||||||
New Password:
|
<div class="form-group">
|
||||||
<input type="password" wicket:id="password" />
|
<label>New Password</label>
|
||||||
<br />
|
<input type="password" wicket:id="password" />
|
||||||
Confirm:
|
</div>
|
||||||
<input type="password" wicket:id="confirm" />
|
<div class="form-group">
|
||||||
<br />
|
<label>Confirm</label>
|
||||||
<input type="submit" class="btn btn-primary" value="Submit" />
|
<input type="password" wicket:id="confirm" />
|
||||||
<input type="button" class="btn" wicket:id="cancel" value="Home page" />
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="submit" class="btn btn-primary" value="Submit" />
|
||||||
|
<input type="button" class="btn btn-default" wicket:id="cancel" value="Home page" />
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,15 +2,18 @@
|
|||||||
<body>
|
<body>
|
||||||
<wicket:extend>
|
<wicket:extend>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="text-center">
|
<div class="col-xs-6 col-xs-offset-3 text-center">
|
||||||
<img src="images/logo_2.png" />
|
<img src="images/logo_2.png" />
|
||||||
<div wicket:id="feedback"></div>
|
<div wicket:id="feedback"></div>
|
||||||
<form wicket:id="form">
|
<form wicket:id="form" class="form-horizontal">
|
||||||
Email:
|
<div class="form-group">
|
||||||
<input type="email" wicket:id="email" />
|
<label>Email</label>
|
||||||
<br />
|
<input type="email" wicket:id="email" class="form-control"/>
|
||||||
<input type="submit" class="btn btn-primary" value="Submit" />
|
</div>
|
||||||
<input type="button" class="btn" wicket:id="cancel" value="Cancel" />
|
<div class="form-group">
|
||||||
|
<input type="submit" class="btn btn-primary" value="Submit" />
|
||||||
|
<input type="button" class="btn btn-default" wicket:id="cancel" value="Cancel" />
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
<body>
|
<body>
|
||||||
<wicket:extend>
|
<wicket:extend>
|
||||||
<div class="welcome">
|
<div class="welcome">
|
||||||
<div class="container-fluid">
|
<div class="container">
|
||||||
<div class="row-fluid header">
|
<div class="row header">
|
||||||
<div class="pull-left">
|
<div class="pull-left">
|
||||||
<a wicket:id="logo-link">
|
<a wicket:id="logo-link">
|
||||||
<img src="images/logo_2.png"></img>
|
<img src="images/logo_2.png"></img>
|
||||||
@@ -23,14 +23,14 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row-fluid">
|
<div class="row">
|
||||||
<div class="span6">
|
<div class="col-md-6">
|
||||||
<div class="well" id="login-panel">
|
<div class="well" id="login-panel">
|
||||||
<h3>Login</h3>
|
<h3>Login</h3>
|
||||||
<span wicket:id="login"></span>
|
<span wicket:id="login"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="span6" wicket:enclosure="register">
|
<div class="col-md-6" wicket:enclosure="register">
|
||||||
<div class="well" id="register-panel">
|
<div class="well" id="register-panel">
|
||||||
<h3>Register</h3>
|
<h3>Register</h3>
|
||||||
<span wicket:id="register"></span>
|
<span wicket:id="register"></span>
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<div class="row-fluid">
|
<div class="row">
|
||||||
<span>
|
<span>
|
||||||
©
|
©
|
||||||
<a href="http://www.commafeed.com" target="_blank">CommaFeed</a>
|
<a href="http://www.commafeed.com" target="_blank">CommaFeed</a>
|
||||||
|
|||||||
@@ -4,24 +4,20 @@
|
|||||||
<wicket:panel>
|
<wicket:panel>
|
||||||
<span wicket:id="feedback"></span>
|
<span wicket:id="feedback"></span>
|
||||||
<form wicket:id="signInForm">
|
<form wicket:id="signInForm">
|
||||||
<div class="control-group">
|
<div class="form-group">
|
||||||
<label class="control-label" for="username">User Name</label>
|
<label for="username">User Name</label>
|
||||||
<div class="controls">
|
<input type="text" id="username" wicket:id="username" class="form-control"></input>
|
||||||
<input type="text" id="username" wicket:id="username" class="input-block-level"></input>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group">
|
<div class="form-group">
|
||||||
<label class="control-label" for="password">Password</label>
|
<label for="password">Password</label>
|
||||||
<div class="controls">
|
<input type="password" id="password" wicket:id="password" class="form-control"></input>
|
||||||
<input type="password" id="password" wicket:id="password" class="input-block-level"></input>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<p class="help-block" wicket:id="rememberMeRow">
|
<div wicket:id="rememberMeRow">
|
||||||
<label class="checkbox">
|
<label>
|
||||||
<input wicket:id="rememberMe" type="checkbox" />
|
<input wicket:id="rememberMe" type="checkbox" />
|
||||||
Remember me
|
Remember me
|
||||||
</label>
|
</label>
|
||||||
</p>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input type="submit" class="btn btn-primary" value="Log in" />
|
<input type="submit" class="btn btn-primary" value="Log in" />
|
||||||
<a wicket:id="recover" class="pull-right">Forgot password?</a>
|
<a wicket:id="recover" class="pull-right">Forgot password?</a>
|
||||||
|
|||||||
@@ -4,23 +4,17 @@
|
|||||||
<wicket:panel>
|
<wicket:panel>
|
||||||
<div wicket:id="feedback"></div>
|
<div wicket:id="feedback"></div>
|
||||||
<form wicket:id="form" autocomplete="off">
|
<form wicket:id="form" autocomplete="off">
|
||||||
<div class="control-group">
|
<div class="form-group">
|
||||||
<label class="control-label">User Name</label>
|
<label>User Name</label>
|
||||||
<div class="controls">
|
<input type="text" wicket:id="name" class="form-control"></input>
|
||||||
<input type="text" wicket:id="name" class="input-block-level"></input>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group">
|
<div class="form-group">
|
||||||
<label class="control-label">Password</label>
|
<label>Password</label>
|
||||||
<div class="controls">
|
<input type="password" wicket:id="password" class="form-control"></input>
|
||||||
<input type="password" wicket:id="password" class="input-block-level"></input>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group">
|
<div class="form-group">
|
||||||
<label class="control-label">Email address (used for password recovery only)</label>
|
<label>Email address (used for password recovery only)</label>
|
||||||
<div class="controls">
|
<input type="email" wicket:id="email" class="form-control"></input>
|
||||||
<input type="email" wicket:id="email" class="input-block-level"></input>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input type="submit" class="btn btn-primary" value="Register" />
|
<input type="submit" class="btn btn-primary" value="Register" />
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.commafeed.frontend.resources;
|
||||||
|
|
||||||
|
import ro.isdc.wro.model.resource.processor.impl.css.CssUrlRewritingProcessor;
|
||||||
|
|
||||||
|
public class CustomCssUrlRewritingProcessor extends CssUrlRewritingProcessor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ignore webjar image replacements since they won't be available at runtime anyway
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected String replaceImageUrl(String cssUri, String imageUrl) {
|
||||||
|
if (cssUri.startsWith("webjar:")) {
|
||||||
|
return imageUrl;
|
||||||
|
}
|
||||||
|
return super.replaceImageUrl(cssUri, imageUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ public class WroAdditionalProvider implements ProcessorProvider {
|
|||||||
map.put("sassOnlyProcessor", new SassOnlyProcessor());
|
map.put("sassOnlyProcessor", new SassOnlyProcessor());
|
||||||
map.put("sassImport", new SassImportProcessor());
|
map.put("sassImport", new SassImportProcessor());
|
||||||
map.put("timestamp", new TimestampProcessor());
|
map.put("timestamp", new TimestampProcessor());
|
||||||
|
map.put("cssUrlRewriting", new CustomCssUrlRewritingProcessor());
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ public class WroManagerFactory extends ConfigurableWroManagerFactory {
|
|||||||
map.put("sassOnlyProcessor", new SassOnlyProcessor());
|
map.put("sassOnlyProcessor", new SassOnlyProcessor());
|
||||||
map.put("sassImport", new SassImportProcessor());
|
map.put("sassImport", new SassImportProcessor());
|
||||||
map.put("timestamp", new TimestampProcessor());
|
map.put("timestamp", new TimestampProcessor());
|
||||||
|
map.put("cssUrlRewriting", new CustomCssUrlRewritingProcessor());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
@@ -130,4 +146,5 @@ public abstract class AbstractREST {
|
|||||||
boolean authorized = roles.hasAnyRole(new Roles(requiredRole.name()));
|
boolean authorized = roles.hasAnyRole(new Roles(requiredRole.name()));
|
||||||
return authorized;
|
return authorized;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +1,40 @@
|
|||||||
package com.commafeed.frontend.rest.resources;
|
package com.commafeed.frontend.rest.resources;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.ws.rs.DefaultValue;
|
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.POST;
|
import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.QueryParam;
|
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.Response.Status;
|
import javax.ws.rs.core.Response.Status;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.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.UserDAO;
|
import com.commafeed.backend.dao.UserDAO;
|
||||||
import com.commafeed.backend.dao.UserRoleDAO;
|
import com.commafeed.backend.dao.UserRoleDAO;
|
||||||
import com.commafeed.backend.feeds.FeedRefreshTaskGiver;
|
import com.commafeed.backend.feeds.FeedRefreshTaskGiver;
|
||||||
import com.commafeed.backend.feeds.FeedRefreshUpdater;
|
import com.commafeed.backend.feeds.FeedRefreshUpdater;
|
||||||
import com.commafeed.backend.feeds.FeedRefreshWorker;
|
import com.commafeed.backend.feeds.FeedRefreshWorker;
|
||||||
import com.commafeed.backend.model.ApplicationSettings;
|
import com.commafeed.backend.model.ApplicationSettings;
|
||||||
import com.commafeed.backend.model.Feed;
|
|
||||||
import com.commafeed.backend.model.User;
|
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.UserModel;
|
import com.commafeed.frontend.model.UserModel;
|
||||||
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.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import com.wordnik.swagger.annotations.Api;
|
import com.wordnik.swagger.annotations.Api;
|
||||||
@@ -70,10 +62,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,26 +218,24 @@ 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());
|
|
||||||
|
|
||||||
|
@Path("/cleanup/entries")
|
||||||
|
@GET
|
||||||
|
@ApiOperation(value = "Entries cleanup", notes = "Delete entries without subscriptions")
|
||||||
|
public Response cleanupEntries() {
|
||||||
|
Map<String, Long> map = Maps.newHashMap();
|
||||||
|
map.put("entries_without_subscriptions", cleaner.cleanEntriesWithoutSubscriptions());
|
||||||
return Response.ok(map).build();
|
return Response.ok(map).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("/cleanup/feeds")
|
@Path("/cleanup/feeds")
|
||||||
@GET
|
@GET
|
||||||
@ApiOperation(value = "Feeds cleanup", notes = "Delete feeds without subscriptions and entries without feeds")
|
@ApiOperation(value = "Feeds cleanup", notes = "Delete feeds without subscriptions")
|
||||||
public Response cleanupFeeds() {
|
public Response cleanupFeeds() {
|
||||||
Map<String, Long> map = Maps.newHashMap();
|
Map<String, Long> map = Maps.newHashMap();
|
||||||
map.put("feeds_without_subscriptions", cleaner.cleanFeedsWithoutSubscriptions());
|
map.put("feeds_without_subscriptions", cleaner.cleanFeedsWithoutSubscriptions());
|
||||||
@@ -261,44 +251,4 @@ public class AdminREST extends AbstractREST {
|
|||||||
return Response.ok(map).build();
|
return Response.ok(map).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("/cleanup/entries")
|
|
||||||
@GET
|
|
||||||
@ApiOperation(value = "Entries cleanup", notes = "Delete entries older than given date")
|
|
||||||
public Response cleanupEntries(@QueryParam("days") @DefaultValue("30") int days) {
|
|
||||||
Map<String, Long> map = Maps.newHashMap();
|
|
||||||
map.put("old_entries", cleaner.cleanEntriesOlderThan(days, TimeUnit.DAYS));
|
|
||||||
return Response.ok(map).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Path("/cleanup/findDuplicateFeeds")
|
|
||||||
@GET
|
|
||||||
@ApiOperation(value = "Find duplicate feeds")
|
|
||||||
public Response findDuplicateFeeds(@QueryParam("mode") DuplicateMode mode, @QueryParam("page") int page,
|
|
||||||
@QueryParam("limit") int limit, @QueryParam("minCount") long minCount) {
|
|
||||||
List<FeedCount> list = feedDAO.findDuplicates(mode, limit * page, limit, minCount);
|
|
||||||
return Response.ok(list).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Path("/cleanup/merge")
|
|
||||||
@POST
|
|
||||||
@ApiOperation(value = "Merge feeds", notes = "Merge feeds together")
|
|
||||||
public Response mergeFeeds(@ApiParam(required = true) FeedMergeRequest request) {
|
|
||||||
Feed into = feedDAO.findById(request.getIntoFeedId());
|
|
||||||
if (into == null) {
|
|
||||||
return Response.status(Status.BAD_REQUEST).entity("'into feed' not found").build();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Feed> feeds = Lists.newArrayList();
|
|
||||||
for (Long feedId : request.getFeedIds()) {
|
|
||||||
Feed feed = feedDAO.findById(feedId);
|
|
||||||
feeds.add(feed);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (feeds.isEmpty()) {
|
|
||||||
return Response.status(Status.BAD_REQUEST).entity("'from feeds' empty").build();
|
|
||||||
}
|
|
||||||
|
|
||||||
cleaner.mergeFeeds(into, feeds);
|
|
||||||
return Response.ok().build();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ import javax.ws.rs.core.Response.Status;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import org.apache.commons.collections.CollectionUtils;
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
import org.apache.commons.lang.ObjectUtils;
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
|
|
||||||
import com.commafeed.backend.cache.CacheService;
|
import com.commafeed.backend.cache.CacheService;
|
||||||
import com.commafeed.backend.dao.FeedCategoryDAO;
|
import com.commafeed.backend.dao.FeedCategoryDAO;
|
||||||
@@ -106,7 +106,8 @@ public class CategoryREST extends AbstractREST {
|
|||||||
@ApiParam(
|
@ApiParam(
|
||||||
value = "search for keywords in either the title or the content of the entries, separated by spaces, 3 characters minimum") @QueryParam("keywords") String keywords,
|
value = "search for keywords in either the title or the content of the entries, separated by spaces, 3 characters minimum") @QueryParam("keywords") String keywords,
|
||||||
@ApiParam(value = "return only entry ids") @DefaultValue("false") @QueryParam("onlyIds") boolean onlyIds,
|
@ApiParam(value = "return only entry ids") @DefaultValue("false") @QueryParam("onlyIds") boolean onlyIds,
|
||||||
@ApiParam(value = "comma-separated list of excluded subscription ids") @QueryParam("excludedSubscriptionIds") String excludedSubscriptionIds) {
|
@ApiParam(value = "comma-separated list of excluded subscription ids") @QueryParam("excludedSubscriptionIds") String excludedSubscriptionIds,
|
||||||
|
@ApiParam(value = "keep only entries tagged with this tag") @QueryParam("tag") String tag) {
|
||||||
|
|
||||||
Preconditions.checkNotNull(readType);
|
Preconditions.checkNotNull(readType);
|
||||||
|
|
||||||
@@ -135,11 +136,11 @@ public class CategoryREST extends AbstractREST {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ALL.equals(id)) {
|
if (ALL.equals(id)) {
|
||||||
entries.setName("All");
|
entries.setName(ObjectUtils.defaultIfNull(tag, "All"));
|
||||||
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(getUser());
|
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(getUser());
|
||||||
removeExcludedSubscriptions(subs, excludedIds);
|
removeExcludedSubscriptions(subs, excludedIds);
|
||||||
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(subs, unreadOnly, keywords, newerThanDate, offset,
|
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(getUser(), subs, unreadOnly, keywords, newerThanDate,
|
||||||
limit + 1, order, true, onlyIds);
|
offset, limit + 1, order, true, onlyIds, tag);
|
||||||
|
|
||||||
for (FeedEntryStatus status : list) {
|
for (FeedEntryStatus status : list) {
|
||||||
entries.getEntries().add(
|
entries.getEntries().add(
|
||||||
@@ -161,8 +162,8 @@ public class CategoryREST extends AbstractREST {
|
|||||||
List<FeedCategory> categories = feedCategoryDAO.findAllChildrenCategories(getUser(), parent);
|
List<FeedCategory> categories = feedCategoryDAO.findAllChildrenCategories(getUser(), parent);
|
||||||
List<FeedSubscription> subs = feedSubscriptionDAO.findByCategories(getUser(), categories);
|
List<FeedSubscription> subs = feedSubscriptionDAO.findByCategories(getUser(), categories);
|
||||||
removeExcludedSubscriptions(subs, excludedIds);
|
removeExcludedSubscriptions(subs, excludedIds);
|
||||||
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(subs, unreadOnly, keywords, newerThanDate, offset,
|
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(getUser(), subs, unreadOnly, keywords, newerThanDate,
|
||||||
limit + 1, order, true, onlyIds);
|
offset, limit + 1, order, true, onlyIds, tag);
|
||||||
|
|
||||||
for (FeedEntryStatus status : list) {
|
for (FeedEntryStatus status : list) {
|
||||||
entries.getEntries().add(
|
entries.getEntries().add(
|
||||||
@@ -170,8 +171,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;
|
||||||
@@ -181,6 +183,7 @@ public class CategoryREST extends AbstractREST {
|
|||||||
}
|
}
|
||||||
|
|
||||||
entries.setTimestamp(System.currentTimeMillis());
|
entries.setTimestamp(System.currentTimeMillis());
|
||||||
|
entries.setIgnoredReadStatus(STARRED.equals(id) || keywords != null || tag != null);
|
||||||
FeedUtils.removeUnwantedFromSearch(entries.getEntries(), keywords);
|
FeedUtils.removeUnwantedFromSearch(entries.getEntries(), keywords);
|
||||||
return Response.ok(entries).build();
|
return Response.ok(entries).build();
|
||||||
}
|
}
|
||||||
@@ -191,16 +194,24 @@ public class CategoryREST extends AbstractREST {
|
|||||||
@Produces(MediaType.APPLICATION_XML)
|
@Produces(MediaType.APPLICATION_XML)
|
||||||
@SecurityCheck(value = Role.USER, apiKeyAllowed = true)
|
@SecurityCheck(value = Role.USER, apiKeyAllowed = true)
|
||||||
public Response getCategoryEntriesAsFeed(
|
public Response getCategoryEntriesAsFeed(
|
||||||
@ApiParam(value = "id of the category, 'all' or 'starred'", required = true) @QueryParam("id") String id) {
|
@ApiParam(value = "id of the category, 'all' or 'starred'", required = true) @QueryParam("id") String id,
|
||||||
|
@ApiParam(value = "all entries or only unread ones", allowableValues = "all,unread", required = true) @DefaultValue("all") @QueryParam("readType") ReadingMode readType,
|
||||||
|
@ApiParam(value = "only entries newer than this") @QueryParam("newerThan") Long newerThan,
|
||||||
|
@ApiParam(value = "offset for paging") @DefaultValue("0") @QueryParam("offset") int offset,
|
||||||
|
@ApiParam(value = "limit for paging, default 20, maximum 1000") @DefaultValue("20") @QueryParam("limit") int limit,
|
||||||
|
@ApiParam(value = "date ordering", allowableValues = "asc,desc") @QueryParam("order") @DefaultValue("desc") ReadingOrder order,
|
||||||
|
@ApiParam(
|
||||||
|
value = "search for keywords in either the title or the content of the entries, separated by spaces, 3 characters minimum") @QueryParam("keywords") String keywords,
|
||||||
|
@ApiParam(value = "return only entry ids") @DefaultValue("false") @QueryParam("onlyIds") boolean onlyIds,
|
||||||
|
@ApiParam(value = "comma-separated list of excluded subscription ids") @QueryParam("excludedSubscriptionIds") String excludedSubscriptionIds,
|
||||||
|
@ApiParam(value = "keep only entries tagged with this tag") @QueryParam("tag") String tag) {
|
||||||
|
|
||||||
Preconditions.checkNotNull(id);
|
Response response = getCategoryEntries(id, readType, newerThan, offset, limit, order, keywords, onlyIds, excludedSubscriptionIds,
|
||||||
|
tag);
|
||||||
ReadingMode readType = ReadingMode.all;
|
if (response.getStatus() != Status.OK.getStatusCode()) {
|
||||||
ReadingOrder order = ReadingOrder.desc;
|
return response;
|
||||||
int offset = 0;
|
}
|
||||||
int limit = 20;
|
Entries entries = (Entries) response.getEntity();
|
||||||
|
|
||||||
Entries entries = (Entries) getCategoryEntries(id, readType, null, offset, limit, order, null, false, null).getEntity();
|
|
||||||
|
|
||||||
SyndFeed feed = new SyndFeedImpl();
|
SyndFeed feed = new SyndFeedImpl();
|
||||||
feed.setFeedType("rss_2.0");
|
feed.setFeedType("rss_2.0");
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
package com.commafeed.frontend.rest.resources;
|
package com.commafeed.frontend.rest.resources;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.POST;
|
import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
import com.commafeed.backend.dao.FeedEntryStatusDAO;
|
import com.commafeed.backend.dao.FeedEntryStatusDAO;
|
||||||
|
import com.commafeed.backend.dao.FeedEntryTagDAO;
|
||||||
import com.commafeed.backend.dao.FeedSubscriptionDAO;
|
import com.commafeed.backend.dao.FeedSubscriptionDAO;
|
||||||
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.FeedEntryTagService;
|
||||||
import com.commafeed.frontend.model.request.MarkRequest;
|
import com.commafeed.frontend.model.request.MarkRequest;
|
||||||
import com.commafeed.frontend.model.request.MultipleMarkRequest;
|
import com.commafeed.frontend.model.request.MultipleMarkRequest;
|
||||||
import com.commafeed.frontend.model.request.StarRequest;
|
import com.commafeed.frontend.model.request.StarRequest;
|
||||||
|
import com.commafeed.frontend.model.request.TagRequest;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.wordnik.swagger.annotations.Api;
|
import com.wordnik.swagger.annotations.Api;
|
||||||
import com.wordnik.swagger.annotations.ApiOperation;
|
import com.wordnik.swagger.annotations.ApiOperation;
|
||||||
@@ -30,6 +36,12 @@ public class EntryREST extends AbstractREST {
|
|||||||
@Inject
|
@Inject
|
||||||
FeedSubscriptionDAO feedSubscriptionDAO;
|
FeedSubscriptionDAO feedSubscriptionDAO;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FeedEntryTagDAO feedEntryTagDAO;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FeedEntryTagService feedEntryTagService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ApplicationSettingsService applicationSettingsService;
|
ApplicationSettingsService applicationSettingsService;
|
||||||
|
|
||||||
@@ -71,4 +83,24 @@ public class EntryREST extends AbstractREST {
|
|||||||
return Response.ok().build();
|
return Response.ok().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Path("/tags")
|
||||||
|
@GET
|
||||||
|
@ApiOperation(value = "Get list of tags for the user", notes = "Get list of tags for the user")
|
||||||
|
public Response getTags() {
|
||||||
|
List<String> tags = feedEntryTagDAO.findByUser(getUser());
|
||||||
|
return Response.ok(tags).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Path("/tag")
|
||||||
|
@POST
|
||||||
|
@ApiOperation(value = "Mark a feed entry", notes = "Mark a feed entry as read/unread")
|
||||||
|
public Response tagFeedEntry(@ApiParam(value = "Tag Request", required = true) TagRequest req) {
|
||||||
|
Preconditions.checkNotNull(req);
|
||||||
|
Preconditions.checkNotNull(req.getEntryId());
|
||||||
|
|
||||||
|
feedEntryTagService.updateTags(getUser(), req.getEntryId(), req.getTags());
|
||||||
|
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -71,6 +71,7 @@ import com.commafeed.frontend.model.request.IDRequest;
|
|||||||
import com.commafeed.frontend.model.request.MarkRequest;
|
import com.commafeed.frontend.model.request.MarkRequest;
|
||||||
import com.commafeed.frontend.model.request.SubscribeRequest;
|
import com.commafeed.frontend.model.request.SubscribeRequest;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.base.Throwables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.sun.syndication.feed.opml.Opml;
|
import com.sun.syndication.feed.opml.Opml;
|
||||||
import com.sun.syndication.feed.synd.SyndEntry;
|
import com.sun.syndication.feed.synd.SyndEntry;
|
||||||
@@ -167,8 +168,8 @@ public class FeedREST extends AbstractREST {
|
|||||||
entries.setErrorCount(subscription.getFeed().getErrorCount());
|
entries.setErrorCount(subscription.getFeed().getErrorCount());
|
||||||
entries.setFeedLink(subscription.getFeed().getLink());
|
entries.setFeedLink(subscription.getFeed().getLink());
|
||||||
|
|
||||||
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(Arrays.asList(subscription), unreadOnly, keywords,
|
List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(getUser(), Arrays.asList(subscription), unreadOnly,
|
||||||
newerThanDate, offset, limit + 1, order, true, onlyIds);
|
keywords, newerThanDate, offset, limit + 1, order, true, onlyIds, null);
|
||||||
|
|
||||||
for (FeedEntryStatus status : list) {
|
for (FeedEntryStatus status : list) {
|
||||||
entries.getEntries().add(
|
entries.getEntries().add(
|
||||||
@@ -181,9 +182,12 @@ 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());
|
||||||
|
entries.setIgnoredReadStatus(keywords != null);
|
||||||
FeedUtils.removeUnwantedFromSearch(entries.getEntries(), keywords);
|
FeedUtils.removeUnwantedFromSearch(entries.getEntries(), keywords);
|
||||||
return Response.ok(entries).build();
|
return Response.ok(entries).build();
|
||||||
}
|
}
|
||||||
@@ -193,16 +197,22 @@ public class FeedREST extends AbstractREST {
|
|||||||
@ApiOperation(value = "Get feed entries as a feed", notes = "Get a feed of feed entries")
|
@ApiOperation(value = "Get feed entries as a feed", notes = "Get a feed of feed entries")
|
||||||
@Produces(MediaType.APPLICATION_XML)
|
@Produces(MediaType.APPLICATION_XML)
|
||||||
@SecurityCheck(value = Role.USER, apiKeyAllowed = true)
|
@SecurityCheck(value = Role.USER, apiKeyAllowed = true)
|
||||||
public Response getFeedEntriesAsFeed(@ApiParam(value = "id of the feed", required = true) @QueryParam("id") String id) {
|
public Response getFeedEntriesAsFeed(
|
||||||
|
@ApiParam(value = "id of the feed", required = true) @QueryParam("id") String id,
|
||||||
|
@ApiParam(value = "all entries or only unread ones", allowableValues = "all,unread", required = true) @DefaultValue("all") @QueryParam("readType") ReadingMode readType,
|
||||||
|
@ApiParam(value = "only entries newer than this") @QueryParam("newerThan") Long newerThan,
|
||||||
|
@ApiParam(value = "offset for paging") @DefaultValue("0") @QueryParam("offset") int offset,
|
||||||
|
@ApiParam(value = "limit for paging, default 20, maximum 1000") @DefaultValue("20") @QueryParam("limit") int limit,
|
||||||
|
@ApiParam(value = "date ordering", allowableValues = "asc,desc") @QueryParam("order") @DefaultValue("desc") ReadingOrder order,
|
||||||
|
@ApiParam(
|
||||||
|
value = "search for keywords in either the title or the content of the entries, separated by spaces, 3 characters minimum") @QueryParam("keywords") String keywords,
|
||||||
|
@ApiParam(value = "return only entry ids") @DefaultValue("false") @QueryParam("onlyIds") boolean onlyIds) {
|
||||||
|
|
||||||
Preconditions.checkNotNull(id);
|
Response response = getFeedEntries(id, readType, newerThan, offset, limit, order, keywords, onlyIds);
|
||||||
|
if (response.getStatus() != Status.OK.getStatusCode()) {
|
||||||
ReadingMode readType = ReadingMode.all;
|
return response;
|
||||||
ReadingOrder order = ReadingOrder.desc;
|
}
|
||||||
int offset = 0;
|
Entries entries = (Entries) response.getEntity();
|
||||||
int limit = 20;
|
|
||||||
|
|
||||||
Entries entries = (Entries) getFeedEntries(id, readType, null, offset, limit, order, null, false).getEntity();
|
|
||||||
|
|
||||||
SyndFeed feed = new SyndFeedImpl();
|
SyndFeed feed = new SyndFeedImpl();
|
||||||
feed.setFeedType("rss_2.0");
|
feed.setFeedType("rss_2.0");
|
||||||
@@ -235,7 +245,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) {
|
||||||
@@ -256,7 +266,8 @@ public class FeedREST extends AbstractREST {
|
|||||||
try {
|
try {
|
||||||
info = fetchFeedInternal(req.getUrl());
|
info = fetchFeedInternal(req.getUrl());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build();
|
return Response.status(Status.INTERNAL_SERVER_ERROR).entity(Throwables.getStackTraceAsString(Throwables.getRootCause(e)))
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
return Response.ok(info).build();
|
return Response.ok(info).build();
|
||||||
}
|
}
|
||||||
@@ -265,11 +276,7 @@ public class FeedREST extends AbstractREST {
|
|||||||
@GET
|
@GET
|
||||||
@ApiOperation(value = "Queue all feeds of the user for refresh", notes = "Manually add all feeds of the user to the refresh queue")
|
@ApiOperation(value = "Queue all feeds of the user for refresh", notes = "Manually add all feeds of the user to the refresh queue")
|
||||||
public Response queueAllForRefresh() {
|
public Response queueAllForRefresh() {
|
||||||
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(getUser());
|
feedSubscriptionService.refreshAll(getUser());
|
||||||
for (FeedSubscription sub : subs) {
|
|
||||||
Feed feed = sub.getFeed();
|
|
||||||
taskGiver.add(feed, true);
|
|
||||||
}
|
|
||||||
return Response.ok().build();
|
return Response.ok().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,8 +375,10 @@ public class FeedREST extends AbstractREST {
|
|||||||
try {
|
try {
|
||||||
url = fetchFeedInternal(url).getUrl();
|
url = fetchFeedInternal(url).getUrl();
|
||||||
|
|
||||||
FeedCategory category = CategoryREST.ALL.equals(req.getCategoryId()) ? null : feedCategoryDAO.findById(Long.valueOf(req
|
FeedCategory category = null;
|
||||||
.getCategoryId()));
|
if (req.getCategoryId() != null && !CategoryREST.ALL.equals(req.getCategoryId())) {
|
||||||
|
category = feedCategoryDAO.findById(Long.valueOf(req.getCategoryId()));
|
||||||
|
}
|
||||||
FeedInfo info = fetchFeedInternal(url);
|
FeedInfo info = fetchFeedInternal(url);
|
||||||
feedSubscriptionService.subscribe(getUser(), info.getUrl(), req.getTitle(), category);
|
feedSubscriptionService.subscribe(getUser(), info.getUrl(), req.getTitle(), category);
|
||||||
} 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;
|
||||||
@@ -74,20 +74,44 @@ public class UserREST extends AbstractREST {
|
|||||||
s.setReadingOrder(settings.getReadingOrder().name());
|
s.setReadingOrder(settings.getReadingOrder().name());
|
||||||
s.setViewMode(settings.getViewMode().name());
|
s.setViewMode(settings.getViewMode().name());
|
||||||
s.setShowRead(settings.isShowRead());
|
s.setShowRead(settings.isShowRead());
|
||||||
s.setSocialButtons(settings.isSocialButtons());
|
|
||||||
|
s.setEmail(settings.isEmail());
|
||||||
|
s.setGmail(settings.isGmail());
|
||||||
|
s.setFacebook(settings.isFacebook());
|
||||||
|
s.setTwitter(settings.isTwitter());
|
||||||
|
s.setGoogleplus(settings.isGoogleplus());
|
||||||
|
s.setTumblr(settings.isTumblr());
|
||||||
|
s.setPocket(settings.isPocket());
|
||||||
|
s.setInstapaper(settings.isInstapaper());
|
||||||
|
s.setBuffer(settings.isBuffer());
|
||||||
|
s.setReadability(settings.isReadability());
|
||||||
|
|
||||||
s.setScrollMarks(settings.isScrollMarks());
|
s.setScrollMarks(settings.isScrollMarks());
|
||||||
s.setTheme(settings.getTheme());
|
s.setTheme(settings.getTheme());
|
||||||
s.setCustomCss(settings.getCustomCss());
|
s.setCustomCss(settings.getCustomCss());
|
||||||
s.setLanguage(settings.getLanguage());
|
s.setLanguage(settings.getLanguage());
|
||||||
|
s.setScrollSpeed(settings.getScrollSpeed());
|
||||||
} else {
|
} else {
|
||||||
s.setReadingMode(ReadingMode.unread.name());
|
s.setReadingMode(ReadingMode.unread.name());
|
||||||
s.setReadingOrder(ReadingOrder.desc.name());
|
s.setReadingOrder(ReadingOrder.desc.name());
|
||||||
s.setViewMode(ViewMode.title.name());
|
s.setViewMode(ViewMode.title.name());
|
||||||
s.setShowRead(true);
|
s.setShowRead(true);
|
||||||
s.setTheme("default");
|
s.setTheme("default");
|
||||||
s.setSocialButtons(true);
|
|
||||||
|
s.setEmail(true);
|
||||||
|
s.setGmail(true);
|
||||||
|
s.setFacebook(true);
|
||||||
|
s.setTwitter(true);
|
||||||
|
s.setGoogleplus(true);
|
||||||
|
s.setTumblr(true);
|
||||||
|
s.setPocket(true);
|
||||||
|
s.setInstapaper(true);
|
||||||
|
s.setBuffer(true);
|
||||||
|
s.setReadability(true);
|
||||||
|
|
||||||
s.setScrollMarks(true);
|
s.setScrollMarks(true);
|
||||||
s.setLanguage("en");
|
s.setLanguage("en");
|
||||||
|
s.setScrollSpeed(400);
|
||||||
}
|
}
|
||||||
return Response.ok(s).build();
|
return Response.ok(s).build();
|
||||||
}
|
}
|
||||||
@@ -114,8 +138,20 @@ public class UserREST extends AbstractREST {
|
|||||||
s.setScrollMarks(settings.isScrollMarks());
|
s.setScrollMarks(settings.isScrollMarks());
|
||||||
s.setTheme(settings.getTheme());
|
s.setTheme(settings.getTheme());
|
||||||
s.setCustomCss(settings.getCustomCss());
|
s.setCustomCss(settings.getCustomCss());
|
||||||
s.setSocialButtons(settings.isSocialButtons());
|
|
||||||
s.setLanguage(settings.getLanguage());
|
s.setLanguage(settings.getLanguage());
|
||||||
|
s.setScrollSpeed(settings.getScrollSpeed());
|
||||||
|
|
||||||
|
s.setEmail(settings.isEmail());
|
||||||
|
s.setGmail(settings.isGmail());
|
||||||
|
s.setFacebook(settings.isFacebook());
|
||||||
|
s.setTwitter(settings.isTwitter());
|
||||||
|
s.setGoogleplus(settings.isGoogleplus());
|
||||||
|
s.setTumblr(settings.isTumblr());
|
||||||
|
s.setPocket(settings.isPocket());
|
||||||
|
s.setInstapaper(settings.isInstapaper());
|
||||||
|
s.setBuffer(settings.isBuffer());
|
||||||
|
s.setReadability(settings.isReadability());
|
||||||
|
|
||||||
userSettingsDAO.saveOrUpdate(s);
|
userSettingsDAO.saveOrUpdate(s);
|
||||||
return Response.ok().build();
|
return Response.ok().build();
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
package liquibase.integration.cdi;
|
|
||||||
|
|
||||||
import java.sql.SQLException;
|
|
||||||
|
|
||||||
import javax.enterprise.event.Observes;
|
|
||||||
import javax.enterprise.inject.Produces;
|
|
||||||
import javax.enterprise.inject.spi.AfterBeanDiscovery;
|
|
||||||
import javax.enterprise.inject.spi.AfterDeploymentValidation;
|
|
||||||
import javax.enterprise.inject.spi.BeanManager;
|
|
||||||
import javax.enterprise.inject.spi.Extension;
|
|
||||||
import javax.sql.DataSource;
|
|
||||||
|
|
||||||
import liquibase.integration.cdi.annotations.LiquibaseType;
|
|
||||||
import liquibase.resource.ResourceAccessor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* temporary fix until https://liquibase.jira.com/browse/CORE-1325 is fixed
|
|
||||||
*/
|
|
||||||
public class CDIBootstrap implements Extension {
|
|
||||||
|
|
||||||
void afterBeanDiscovery(@Observes AfterBeanDiscovery abd, BeanManager bm) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void afterDeploymentValidation(@Observes AfterDeploymentValidation event, BeanManager manager) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Produces
|
|
||||||
@LiquibaseType
|
|
||||||
public CDILiquibaseConfig createConfig() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Produces
|
|
||||||
@LiquibaseType
|
|
||||||
public DataSource createDataSource() throws SQLException {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Produces
|
|
||||||
@LiquibaseType
|
|
||||||
public ResourceAccessor create() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<entity-mappings version="2.0"
|
|
||||||
xmlns="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="
|
|
||||||
http://java.sun.com/xml/ns/persistence/orm
|
|
||||||
http://java.sun.com/xml/ns/persistence/orm_2_0.xsd">
|
|
||||||
|
|
||||||
<named-query name="Statuses.deleteOld">
|
|
||||||
<query>delete from FeedEntryStatus s where s.entryInserted < :date and s.starred = false</query>
|
|
||||||
</named-query>
|
|
||||||
|
|
||||||
</entity-mappings>
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<persistence version="2.0"
|
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="
|
xsi:schemaLocation="
|
||||||
http://java.sun.com/xml/ns/persistence
|
http://java.sun.com/xml/ns/persistence
|
||||||
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
|
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
|
||||||
@@ -9,6 +8,7 @@
|
|||||||
<jta-data-source>${jpa.datasource.name}</jta-data-source>
|
<jta-data-source>${jpa.datasource.name}</jta-data-source>
|
||||||
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
|
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
|
||||||
<properties>
|
<properties>
|
||||||
|
<property name="openejb.jpa.auto-scan" value="true" />
|
||||||
|
|
||||||
<property name="format_sql" value="true" />
|
<property name="format_sql" value="true" />
|
||||||
<property name="use_sql_comments" value="true" />
|
<property name="use_sql_comments" value="true" />
|
||||||
@@ -22,26 +22,17 @@
|
|||||||
|
|
||||||
<property name="hibernate.generate_statistics" value="true" />
|
<property name="hibernate.generate_statistics" value="true" />
|
||||||
|
|
||||||
<property name="hibernate.cache.use_second_level_cache"
|
<property name="hibernate.cache.use_second_level_cache" value="${jpa.cache}" />
|
||||||
value="${jpa.cache}" />
|
|
||||||
<property name="hibernate.cache.use_query_cache" value="${jpa.cache}" />
|
<property name="hibernate.cache.use_query_cache" value="${jpa.cache}" />
|
||||||
|
|
||||||
<property name="hibernate.cache.region.factory_class"
|
<property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.infinispan.InfinispanRegionFactory" />
|
||||||
value="org.hibernate.cache.infinispan.InfinispanRegionFactory" />
|
<property name="hibernate.cache.infinispan.statistics" value="true" />
|
||||||
<property name="hibernate.cache.infinispan.statistics"
|
|
||||||
value="true" />
|
|
||||||
|
|
||||||
<property name="hibernate.cache.infinispan.entity.eviction.strategy"
|
<property name="hibernate.cache.infinispan.entity.eviction.strategy" value="LRU" />
|
||||||
value="LRU" />
|
<property name="hibernate.cache.infinispan.entity.eviction.wake_up_interval" value="2000" />
|
||||||
<property
|
<property name="hibernate.cache.infinispan.entity.eviction.max_entries" value="10000" />
|
||||||
name="hibernate.cache.infinispan.entity.eviction.wake_up_interval"
|
<property name="hibernate.cache.infinispan.entity.expiration.lifespan" value="60000" />
|
||||||
value="2000" />
|
<property name="hibernate.cache.infinispan.entity.expiration.max_idle" value="30000" />
|
||||||
<property name="hibernate.cache.infinispan.entity.eviction.max_entries"
|
|
||||||
value="10000" />
|
|
||||||
<property name="hibernate.cache.infinispan.entity.expiration.lifespan"
|
|
||||||
value="60000" />
|
|
||||||
<property name="hibernate.cache.infinispan.entity.expiration.max_idle"
|
|
||||||
value="30000" />
|
|
||||||
|
|
||||||
</properties>
|
</properties>
|
||||||
</persistence-unit>
|
</persistence-unit>
|
||||||
|
|||||||
@@ -357,6 +357,7 @@
|
|||||||
</changeSet>
|
</changeSet>
|
||||||
|
|
||||||
<changeSet author="athou" id="status-cleanup">
|
<changeSet author="athou" id="status-cleanup">
|
||||||
|
<validCheckSum>7:cf40ae235c2d4086c5fa6ac64102c6a9</validCheckSum>
|
||||||
<delete tableName="FEEDENTRYSTATUSES">
|
<delete tableName="FEEDENTRYSTATUSES">
|
||||||
<where>read_status = false and starred = false</where>
|
<where>read_status = false and starred = false</where>
|
||||||
</delete>
|
</delete>
|
||||||
|
|||||||
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>
|
||||||
51
src/main/resources/changelogs/db.changelog-1.4.xml
Normal file
51
src/main/resources/changelogs/db.changelog-1.4.xml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?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="scroll-speed">
|
||||||
|
<addColumn tableName="USERSETTINGS">
|
||||||
|
<column name="scroll_speed" type="BIGINT" />
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
<changeSet author="athou" id="set-default-scroll-speed">
|
||||||
|
<update tableName="USERSETTINGS">
|
||||||
|
<column name="scroll_speed" valueNumeric="400" />
|
||||||
|
</update>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
<changeSet author="athou" id="create-tags-table">
|
||||||
|
<validCheckSum>7:fdd37bdee09c8fbbcbcd867b05decaae</validCheckSum>
|
||||||
|
<createTable tableName="FEEDENTRYTAGS">
|
||||||
|
<column name="id" type="BIGINT">
|
||||||
|
<constraints nullable="false" primaryKey="true" />
|
||||||
|
</column>
|
||||||
|
<column name="entry_id" type="BIGINT">
|
||||||
|
<constraints nullable="false" />
|
||||||
|
</column>
|
||||||
|
<column name="user_id" type="BIGINT">
|
||||||
|
<constraints nullable="false" />
|
||||||
|
</column>
|
||||||
|
<column name="name" type="VARCHAR(40)">
|
||||||
|
<constraints nullable="false" />
|
||||||
|
</column>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addForeignKeyConstraint constraintName="fk_entry_id" baseTableName="FEEDENTRYTAGS" baseColumnNames="entry_id"
|
||||||
|
referencedTableName="FEEDENTRIES" referencedColumnNames="id" />
|
||||||
|
<addForeignKeyConstraint constraintName="fk_user_id" baseTableName="FEEDENTRYTAGS" baseColumnNames="user_id"
|
||||||
|
referencedTableName="USERS" referencedColumnNames="id" />
|
||||||
|
|
||||||
|
<createIndex tableName="FEEDENTRYTAGS" indexName="user_entry_name_index">
|
||||||
|
<column name="user_id" />
|
||||||
|
<column name="entry_id" />
|
||||||
|
<column name="name" />
|
||||||
|
</createIndex>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
<changeSet author="athou" id="add-full-refresh-timestamp">
|
||||||
|
<addColumn tableName="USERS">
|
||||||
|
<column name="last_full_refresh" type="DATETIME" />
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
||||||
53
src/main/resources/changelogs/db.changelog-1.5.xml
Normal file
53
src/main/resources/changelogs/db.changelog-1.5.xml
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?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.1.xsd">
|
||||||
|
|
||||||
|
<changeSet id="add-detailed-social-options" author="athou">
|
||||||
|
<addColumn tableName="USERSETTINGS">
|
||||||
|
<column name="email" type="BIT"></column>
|
||||||
|
</addColumn>
|
||||||
|
<addColumn tableName="USERSETTINGS">
|
||||||
|
<column name="gmail" type="BIT"></column>
|
||||||
|
</addColumn>
|
||||||
|
<addColumn tableName="USERSETTINGS">
|
||||||
|
<column name="facebook" type="BIT"></column>
|
||||||
|
</addColumn>
|
||||||
|
<addColumn tableName="USERSETTINGS">
|
||||||
|
<column name="twitter" type="BIT"></column>
|
||||||
|
</addColumn>
|
||||||
|
<addColumn tableName="USERSETTINGS">
|
||||||
|
<column name="googleplus" type="BIT"></column>
|
||||||
|
</addColumn>
|
||||||
|
<addColumn tableName="USERSETTINGS">
|
||||||
|
<column name="tumblr" type="BIT"></column>
|
||||||
|
</addColumn>
|
||||||
|
<addColumn tableName="USERSETTINGS">
|
||||||
|
<column name="pocket" type="BIT"></column>
|
||||||
|
</addColumn>
|
||||||
|
<addColumn tableName="USERSETTINGS">
|
||||||
|
<column name="instapaper" type="BIT"></column>
|
||||||
|
</addColumn>
|
||||||
|
<addColumn tableName="USERSETTINGS">
|
||||||
|
<column name="buffer" type="BIT"></column>
|
||||||
|
</addColumn>
|
||||||
|
<addColumn tableName="USERSETTINGS">
|
||||||
|
<column name="readability" type="BIT"></column>
|
||||||
|
</addColumn>
|
||||||
|
|
||||||
|
<dropColumn tableName="USERSETTINGS" columnName="socialButtons" />
|
||||||
|
|
||||||
|
<update tableName="USERSETTINGS">
|
||||||
|
<column name="email" valueBoolean="true"></column>
|
||||||
|
<column name="gmail" valueBoolean="true"></column>
|
||||||
|
<column name="facebook" valueBoolean="true"></column>
|
||||||
|
<column name="twitter" valueBoolean="true"></column>
|
||||||
|
<column name="googleplus" valueBoolean="true"></column>
|
||||||
|
<column name="tumblr" valueBoolean="true"></column>
|
||||||
|
<column name="pocket" valueBoolean="true"></column>
|
||||||
|
<column name="instapaper" valueBoolean="true"></column>
|
||||||
|
<column name="buffer" valueBoolean="true"></column>
|
||||||
|
<column name="readability" valueBoolean="true"></column>
|
||||||
|
</update>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
||||||
@@ -6,5 +6,8 @@
|
|||||||
<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" />
|
||||||
|
<include file="changelogs/db.changelog-1.4.xml" />
|
||||||
|
<include file="changelogs/db.changelog-1.5.xml" />
|
||||||
|
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
@@ -6,6 +6,7 @@ global.download=تحميل
|
|||||||
global.link=رابط
|
global.link=رابط
|
||||||
global.bookmark=مرجعية
|
global.bookmark=مرجعية
|
||||||
global.close=أغلق
|
global.close=أغلق
|
||||||
|
global.tags=Tags ####### Needs translation
|
||||||
|
|
||||||
tree.subscribe=اشترك
|
tree.subscribe=اشترك
|
||||||
tree.import=استورد
|
tree.import=استورد
|
||||||
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=الترتيب حسب التاريخ تصاعدي / ت
|
|||||||
toolbar.titles_only=العناوين فقط
|
toolbar.titles_only=العناوين فقط
|
||||||
toolbar.expanded_view=عرض موسع
|
toolbar.expanded_view=عرض موسع
|
||||||
toolbar.mark_all_as_read=اعتبر الكل مقروء
|
toolbar.mark_all_as_read=اعتبر الكل مقروء
|
||||||
|
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
|
||||||
toolbar.mark_all_older_day=العناصر الأقدم من يوم
|
toolbar.mark_all_older_day=العناصر الأقدم من يوم
|
||||||
toolbar.mark_all_older_week=العناصر الأقدم من أسبوع
|
toolbar.mark_all_older_week=العناصر الأقدم من أسبوع
|
||||||
toolbar.mark_all_older_two_weeks=العناصر الأقدم من أسبوعين
|
toolbar.mark_all_older_two_weeks=العناصر الأقدم من أسبوعين
|
||||||
@@ -66,6 +68,8 @@ settings.general.show_unread=Show feeds and categories with no unread entries
|
|||||||
settings.general.social_buttons=Show social sharing buttons
|
settings.general.social_buttons=Show social sharing buttons
|
||||||
settings.general.scroll_marks=In expanded view, scrolling through entries mark them as read
|
settings.general.scroll_marks=In expanded view, scrolling through entries mark them as read
|
||||||
settings.appearance=Appearance
|
settings.appearance=Appearance
|
||||||
|
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
|
||||||
|
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
|
||||||
settings.theme=Theme
|
settings.theme=Theme
|
||||||
settings.submit_your_theme=Submit your theme
|
settings.submit_your_theme=Submit your theme
|
||||||
settings.custom_css=Custom CSS
|
settings.custom_css=Custom CSS
|
||||||
@@ -83,7 +87,10 @@ details.queued_for_refresh=Queued for refresh
|
|||||||
details.feed_url=Feed URL
|
details.feed_url=Feed URL
|
||||||
details.generate_api_key_first=Generate an API key in your profile first.
|
details.generate_api_key_first=Generate an API key in your profile first.
|
||||||
details.unsubscribe=Unsubscribe
|
details.unsubscribe=Unsubscribe
|
||||||
|
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
|
||||||
|
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
|
||||||
details.category_details=Category details
|
details.category_details=Category details
|
||||||
|
details.tag_details=Tag details ####### Needs translation
|
||||||
details.parent_category=Parent category
|
details.parent_category=Parent category
|
||||||
|
|
||||||
profile.user_name=User name
|
profile.user_name=User name
|
||||||
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Generate new API key
|
|||||||
profile.generate_new_api_key_info=Changing password will generate a new API key
|
profile.generate_new_api_key_info=Changing password will generate a new API key
|
||||||
profile.opml_export=OPML export
|
profile.opml_export=OPML export
|
||||||
profile.delete_account=Delete account
|
profile.delete_account=Delete account
|
||||||
|
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
|
||||||
|
|
||||||
about.rest_api=REST API
|
about.rest_api=REST API
|
||||||
about.keyboard_shortcuts=Keyboard shortcuts
|
about.keyboard_shortcuts=Keyboard shortcuts
|
||||||
|
|||||||
156
src/main/resources/i18n/ca.properties
Normal file
156
src/main/resources/i18n/ca.properties
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
global.save=Desa
|
||||||
|
global.cancel=Cancel·la
|
||||||
|
global.delete=Esborra
|
||||||
|
global.required=Requerit
|
||||||
|
global.download=Descarrega
|
||||||
|
global.link=Enllaç
|
||||||
|
global.bookmark=Adreça d'interès
|
||||||
|
global.close=Tancar
|
||||||
|
global.tags=Etiquetes
|
||||||
|
|
||||||
|
tree.subscribe=Subscriure
|
||||||
|
tree.import=Importa
|
||||||
|
tree.new_category=Nova categoria
|
||||||
|
tree.all=Tot
|
||||||
|
tree.starred=Destacats
|
||||||
|
|
||||||
|
subscribe.feed_url=URL del canal
|
||||||
|
subscribe.feed_name=Nom del canal
|
||||||
|
subscribe.category=Categoria
|
||||||
|
|
||||||
|
import.google_reader_prefix=Importaré els canals del teu
|
||||||
|
import.google_reader_suffix= compte.
|
||||||
|
import.google_download=O be, carrega el teu fitxer subscriptions.xml.
|
||||||
|
import.google_download_link=Descarrega'l d'aquí.
|
||||||
|
import.xml_file=Fitxer OPML
|
||||||
|
|
||||||
|
new_category.name=Nom
|
||||||
|
new_category.parent=Arrel
|
||||||
|
|
||||||
|
toolbar.unread=Per llegir
|
||||||
|
toolbar.all=Tots
|
||||||
|
toolbar.previous_entry=Entrada prèvia
|
||||||
|
toolbar.next_entry=Entrada següent
|
||||||
|
toolbar.refresh=Actualitzar
|
||||||
|
toolbar.refresh_all=Força l'actualització de tots els canals
|
||||||
|
toolbar.sort_by_asc_desc=Ordenar per data asc/desc
|
||||||
|
toolbar.titles_only=Només títols
|
||||||
|
toolbar.expanded_view=Vista ampliada
|
||||||
|
toolbar.mark_all_as_read=Marcar tots llegits
|
||||||
|
toolbar.mark_all_older_12_hours=Ítems més vells de 12 hores
|
||||||
|
toolbar.mark_all_older_day=Ítems més vells d'un dia
|
||||||
|
toolbar.mark_all_older_week=Ítems més vells d'una setmana
|
||||||
|
toolbar.mark_all_older_two_weeks=Ítems més vells de dues setmanes
|
||||||
|
toolbar.settings=Configuració
|
||||||
|
toolbar.profile=Perfil
|
||||||
|
toolbar.admin=Admin
|
||||||
|
toolbar.about=Quant a
|
||||||
|
toolbar.logout=Desconnecta't
|
||||||
|
toolbar.donate=Donació
|
||||||
|
|
||||||
|
view.entry_source=de
|
||||||
|
view.entry_author=per
|
||||||
|
view.error_while_loading_feed=Error carregant el canal
|
||||||
|
view.keep_unread=Conserva com a no llegit
|
||||||
|
view.no_unread_items=no té ítems sense llegir.
|
||||||
|
view.mark_up_to_here=Marcar com a llegit fins aquí
|
||||||
|
view.search_for=cercant:
|
||||||
|
view.no_search_results=No hi ha coincidències per les paraules clau sol·licitades
|
||||||
|
|
||||||
|
feedsearch.hint=Introdueix una subscripció...
|
||||||
|
feedsearch.help=Utilitza la tecla de retorn per seleccionar i les tecles de cursor per navegar.
|
||||||
|
feedsearch.result_prefix=Les teves subscripcions:
|
||||||
|
|
||||||
|
settings.general=General
|
||||||
|
settings.general.language=Idioma
|
||||||
|
settings.general.language.contribute=Contribueix amb traduccions
|
||||||
|
settings.general.show_unread=Mostrar canals i categories amb entrades sense llegir
|
||||||
|
settings.general.social_buttons=Mostrar botons per compartir en xarxes socials
|
||||||
|
settings.general.scroll_marks=A la vista ampliada si et desplaces per les entrades les marques com a llegides
|
||||||
|
settings.appearance=Aparença
|
||||||
|
settings.scroll_speed=Velocitat de desplaçament quan navegues entre entrades (en mil·lisegons)
|
||||||
|
settings.scroll_speed.help=Fixa a 0 per desactivar
|
||||||
|
settings.theme=Tema
|
||||||
|
settings.submit_your_theme=Envia un tema
|
||||||
|
settings.custom_css=CSS personalitzat
|
||||||
|
|
||||||
|
details.feed_details=Detalls del canal
|
||||||
|
details.url=URL
|
||||||
|
details.website=Lloc web
|
||||||
|
details.name=Nom
|
||||||
|
details.category=Categoria
|
||||||
|
details.position=Posició
|
||||||
|
details.last_refresh=Darrera actualització
|
||||||
|
details.message=Darrer missatge d'actualització
|
||||||
|
details.next_refresh=Propera actualització
|
||||||
|
details.queued_for_refresh=A la cua d'actualització
|
||||||
|
details.feed_url=URL del canal
|
||||||
|
details.generate_api_key_first=Abans cal que generis una clau API en el teu perfil.
|
||||||
|
details.unsubscribe=Cancel·la la subscripció
|
||||||
|
details.unsubscribe_confirmation=Segur que vols cancel·lar la subscripció del canal?
|
||||||
|
details.delete_category_confirmation=Segur que vols esborrar la categoria?
|
||||||
|
details.category_details=Detalls de la categoria
|
||||||
|
details.tag_details=Detalls de l'etiqueta
|
||||||
|
details.parent_category=Categoria arrel
|
||||||
|
|
||||||
|
profile.user_name=Nom d'usuari
|
||||||
|
profile.email=Adreça electrònica
|
||||||
|
profile.change_password=Canvia la contrasenya
|
||||||
|
profile.confirm_password=Confirma la contrasenya
|
||||||
|
profile.minimum_6_chars=Mínim de 6 caracters
|
||||||
|
profile.passwords_do_not_match=Les contrasenyes no coincideixen
|
||||||
|
profile.api_key=Clau API
|
||||||
|
profile.api_key_not_generated=Encara no s'ha generat
|
||||||
|
profile.generate_new_api_key=Genera una nova clau API
|
||||||
|
profile.generate_new_api_key_info=El canvi de contrasenya generarà una nova clau API
|
||||||
|
profile.opml_export=Exporta OPML
|
||||||
|
profile.delete_account=Esborra el compte
|
||||||
|
profile.delete_account_confirmation=Vols esborrar el teu compte? No ho podràs desfer!
|
||||||
|
|
||||||
|
about.rest_api=REST API
|
||||||
|
about.keyboard_shortcuts=Dreceres de teclat
|
||||||
|
about.version=Versió de CommaFeed
|
||||||
|
about.line1_prefix=CommaFeed és un projecte de codi font obert. El codi font és hostatjat a
|
||||||
|
about.line1_suffix=.
|
||||||
|
about.line2_prefix=Si trobes un problema, si us plau informa'n a la pàgina de problemes del
|
||||||
|
about.line2_suffix=\ projecte.
|
||||||
|
about.line3=Si t'agrada el projecte, pensa en fer un donatiu per recolzar el desenvolupador i per ajudar amb les despeses de l'hostatge del lloc web.
|
||||||
|
about.line4=I pels que preferiu bitcoin, aquí teniu l'adreça
|
||||||
|
about.goodies=Afegitons
|
||||||
|
about.goodies.android_app=App Android
|
||||||
|
about.goodies.subscribe_url=URL de subscripció
|
||||||
|
about.goodies.chrome_extension=Extensió del Chrome
|
||||||
|
about.goodies.firefox_extension=Extensió del Firefox
|
||||||
|
about.goodies.opera_extension=Extensió de l'Opera
|
||||||
|
about.goodies.subscribe_bookmarklet=Afegeix bookmarklet de subscripció (clica)
|
||||||
|
about.goodies.subscribe_bookmarklet_asc=Primer els vells
|
||||||
|
about.goodies.subscribe_bookmarklet_desc=Primer els nous
|
||||||
|
about.goodies.next_unread_bookmarklet=Bookmarklet del proper ítem sense llegir (arrosega a la barra d'adreces d'interès)
|
||||||
|
about.translation=Traducció
|
||||||
|
about.translation.message=Necessitem la teva ajuda per traduir CommaFeed.
|
||||||
|
about.translation.link=Informació per contribuir amb traduccions.
|
||||||
|
about.announcements=Anuncis
|
||||||
|
about.rest_api.line1=CommaFeed funciona amb JAX-RS i AngularJS. Per tant, té disponible una API REST.
|
||||||
|
about.rest_api.link_to_documentation=Enllaç a la documentació.
|
||||||
|
|
||||||
|
about.shortcuts.mouse_middleclick=Clic amb el botó del mig
|
||||||
|
about.shortcuts.open_next_entry=obrir entrada següent
|
||||||
|
about.shortcuts.open_previous_entry=obrir entrada prèvia
|
||||||
|
about.shortcuts.spacebar=espai/majúscula+espai
|
||||||
|
about.shortcuts.move_page_down_up=mou la pàgina avall/amunt
|
||||||
|
about.shortcuts.focus_next_entry=fixa el focus en l'entrada següent entrada sense obrir-la
|
||||||
|
about.shortcuts.focus_previous_entry=fixa el focus en l'entrada prèvia sense obrir-la
|
||||||
|
about.shortcuts.open_next_feed=obrir canal o categoria següent
|
||||||
|
about.shortcuts.open_previous_feed=obrir canal o categoria prèvia
|
||||||
|
about.shortcuts.open_close_current_entry=obre/tanca entrada actual
|
||||||
|
about.shortcuts.open_current_entry_in_new_window=obrir entrada actual en una finestra nova
|
||||||
|
about.shortcuts.open_current_entry_in_new_window_background=obrir entrada actual en una finestra nova en segon pla
|
||||||
|
about.shortcuts.star_unstar=destacar/treure destacat a l'entrada actual
|
||||||
|
about.shortcuts.mark_current_entry=marcar com a llegida/no llegida l'entrada actual
|
||||||
|
about.shortcuts.mark_all_as_read=marcar totes les entrades com a llegides
|
||||||
|
about.shortcuts.open_in_new_tab_mark_as_read=obrir entrada en una pestanya nova i marcar com a llegida
|
||||||
|
about.shortcuts.fullscreen=commutar el mode de pantalla completa
|
||||||
|
about.shortcuts.font_size=incrementar/reduir la mida de la font de l'entrada actual
|
||||||
|
about.shortcuts.go_to_all=anar a la vista de Tot
|
||||||
|
about.shortcuts.go_to_starred=anar a la vista de Destacats
|
||||||
|
about.shortcuts.feed_search=navegar a una subscripció introduint-ne el nom
|
||||||
@@ -6,6 +6,7 @@ global.download = Stáhnout
|
|||||||
global.link = Odkaz
|
global.link = Odkaz
|
||||||
global.bookmark = Záložky
|
global.bookmark = Záložky
|
||||||
global.close = Zavřít
|
global.close = Zavřít
|
||||||
|
global.tags=Tags ####### Needs translation
|
||||||
|
|
||||||
tree.subscribe = Nový odběr
|
tree.subscribe = Nový odběr
|
||||||
tree.import = Importovat
|
tree.import = Importovat
|
||||||
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc = Seřadit podle nejnovějšího/nejstaršího
|
|||||||
toolbar.titles_only = Zobrazit jenom titulky
|
toolbar.titles_only = Zobrazit jenom titulky
|
||||||
toolbar.expanded_view = Rozšířený náhled
|
toolbar.expanded_view = Rozšířený náhled
|
||||||
toolbar.mark_all_as_read = Označit vše jako přečtené
|
toolbar.mark_all_as_read = Označit vše jako přečtené
|
||||||
|
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
|
||||||
toolbar.mark_all_older_day = Položky starší než den
|
toolbar.mark_all_older_day = Položky starší než den
|
||||||
toolbar.mark_all_older_week = Položky starší než týden
|
toolbar.mark_all_older_week = Položky starší než týden
|
||||||
toolbar.mark_all_older_two_weeks = Položky starší než dva týdny
|
toolbar.mark_all_older_two_weeks = Položky starší než dva týdny
|
||||||
@@ -66,6 +68,8 @@ settings.general.show_unread = Zobrazit položky a kategorie z přečtenými pol
|
|||||||
settings.general.social_buttons = Zobrazit možnosti sdílení
|
settings.general.social_buttons = Zobrazit možnosti sdílení
|
||||||
settings.general.scroll_marks = Skrolování v rozšířeném náhledu označí položky jako přečtené
|
settings.general.scroll_marks = Skrolování v rozšířeném náhledu označí položky jako přečtené
|
||||||
settings.appearance = Vzhled
|
settings.appearance = Vzhled
|
||||||
|
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
|
||||||
|
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
|
||||||
settings.theme = Motiv
|
settings.theme = Motiv
|
||||||
settings.submit_your_theme = Nahrát vlastní motiv
|
settings.submit_your_theme = Nahrát vlastní motiv
|
||||||
settings.custom_css = Vlastní motiv (CSS)
|
settings.custom_css = Vlastní motiv (CSS)
|
||||||
@@ -83,7 +87,10 @@ details.queued_for_refresh = Ve frontě na obnovu
|
|||||||
details.feed_url = URL RSS zdroje
|
details.feed_url = URL RSS zdroje
|
||||||
details.generate_api_key_first = Vygenerujte si API klíč na stránce vašeho profilu.
|
details.generate_api_key_first = Vygenerujte si API klíč na stránce vašeho profilu.
|
||||||
details.unsubscribe = Odhlásit odběr.
|
details.unsubscribe = Odhlásit odběr.
|
||||||
|
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
|
||||||
|
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
|
||||||
details.category_details = Detail kategorie
|
details.category_details = Detail kategorie
|
||||||
|
details.tag_details=Tag details ####### Needs translation
|
||||||
details.parent_category = Hlavní kategorie
|
details.parent_category = Hlavní kategorie
|
||||||
|
|
||||||
profile.user_name = Uživatelské jméno
|
profile.user_name = Uživatelské jméno
|
||||||
@@ -98,6 +105,7 @@ profile.generate_new_api_key = Vygenerovat nový API klíč
|
|||||||
profile.generate_new_api_key_info = Změnou hesla vygenerujete nový API klíč
|
profile.generate_new_api_key_info = Změnou hesla vygenerujete nový API klíč
|
||||||
profile.opml_export = exportovat do formátu OPML
|
profile.opml_export = exportovat do formátu OPML
|
||||||
profile.delete_account = Odstranit účet
|
profile.delete_account = Odstranit účet
|
||||||
|
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
|
||||||
|
|
||||||
about.rest_api = REST API
|
about.rest_api = REST API
|
||||||
about.keyboard_shortcuts = Klávesové zkratky
|
about.keyboard_shortcuts = Klávesové zkratky
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ global.download=Lawrlwytho
|
|||||||
global.link=Dolen
|
global.link=Dolen
|
||||||
global.bookmark=Nod tudalen
|
global.bookmark=Nod tudalen
|
||||||
global.close=Cau
|
global.close=Cau
|
||||||
|
global.tags=Tags ####### Needs translation
|
||||||
|
|
||||||
tree.subscribe=Tanysgrifio
|
tree.subscribe=Tanysgrifio
|
||||||
tree.import=Mewnforio
|
tree.import=Mewnforio
|
||||||
@@ -17,10 +18,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 +32,15 @@ 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_12_hours=Items older than 12 hours ####### Needs translation
|
||||||
toolbar.mark_all_older_week=Eitemau sy'n hyn nag wythnos
|
toolbar.mark_all_older_day=Eitemau hyn na diwrnod
|
||||||
toolbar.mark_all_older_two_weeks=Eitemau sy'n hyn na phythefnos
|
toolbar.mark_all_older_week=Eitemau hyn nag wythnos
|
||||||
|
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,44 +48,49 @@ 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.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
|
||||||
|
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
|
||||||
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.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
|
||||||
|
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
|
||||||
details.category_details=Manylion categori
|
details.category_details=Manylion categori
|
||||||
|
details.tag_details=Tag details ####### Needs translation
|
||||||
details.parent_category=Categori rhiant
|
details.parent_category=Categori rhiant
|
||||||
|
|
||||||
profile.user_name=Enw defnyddiwr
|
profile.user_name=Enw defnyddiwr
|
||||||
@@ -91,59 +98,60 @@ 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
|
||||||
|
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ global.download=Hent
|
|||||||
global.link=Link
|
global.link=Link
|
||||||
global.bookmark=Bogmærke
|
global.bookmark=Bogmærke
|
||||||
global.close=Luk
|
global.close=Luk
|
||||||
|
global.tags=Tags ####### Needs translation
|
||||||
|
|
||||||
tree.subscribe=Abonner
|
tree.subscribe=Abonner
|
||||||
tree.import=Importer
|
tree.import=Importer
|
||||||
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sorter efter dato ny/gammel
|
|||||||
toolbar.titles_only=Kun titler
|
toolbar.titles_only=Kun titler
|
||||||
toolbar.expanded_view=Udvidet visning
|
toolbar.expanded_view=Udvidet visning
|
||||||
toolbar.mark_all_as_read=Marker alle som læst
|
toolbar.mark_all_as_read=Marker alle som læst
|
||||||
|
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
|
||||||
toolbar.mark_all_older_day=Artikler ældere end én dag
|
toolbar.mark_all_older_day=Artikler ældere end én dag
|
||||||
toolbar.mark_all_older_week=Artikler ældere end én uge
|
toolbar.mark_all_older_week=Artikler ældere end én uge
|
||||||
toolbar.mark_all_older_two_weeks=Artikler ældere end to uger
|
toolbar.mark_all_older_two_weeks=Artikler ældere end to uger
|
||||||
@@ -66,6 +68,8 @@ settings.general.show_unread=Vis abonnomenter og kategorier med læste artikler
|
|||||||
settings.general.social_buttons=Vis delingsknapper
|
settings.general.social_buttons=Vis delingsknapper
|
||||||
settings.general.scroll_marks=I udvidet visning, marker artikler som læste når der rulles forbi dem
|
settings.general.scroll_marks=I udvidet visning, marker artikler som læste når der rulles forbi dem
|
||||||
settings.appearance=Udseende
|
settings.appearance=Udseende
|
||||||
|
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
|
||||||
|
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
|
||||||
settings.theme=Tema
|
settings.theme=Tema
|
||||||
settings.submit_your_theme=Indsend dit tema
|
settings.submit_your_theme=Indsend dit tema
|
||||||
settings.custom_css=Brugerdefineret CSS
|
settings.custom_css=Brugerdefineret CSS
|
||||||
@@ -83,7 +87,10 @@ details.queued_for_refresh=I kø til opdatering
|
|||||||
details.feed_url=URL for abonnement
|
details.feed_url=URL for abonnement
|
||||||
details.generate_api_key_first=Generer en API nøgle i din profil først.
|
details.generate_api_key_first=Generer en API nøgle i din profil først.
|
||||||
details.unsubscribe=Afmeld abonnement
|
details.unsubscribe=Afmeld abonnement
|
||||||
|
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
|
||||||
|
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
|
||||||
details.category_details=Kategori detaljer
|
details.category_details=Kategori detaljer
|
||||||
|
details.tag_details=Tag details ####### Needs translation
|
||||||
details.parent_category=Overordnet kategori
|
details.parent_category=Overordnet kategori
|
||||||
|
|
||||||
profile.user_name=Brugernavn
|
profile.user_name=Brugernavn
|
||||||
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Generer ny API nøgle
|
|||||||
profile.generate_new_api_key_info=Ændring af adgangskode vil generere en ny API nøgle
|
profile.generate_new_api_key_info=Ændring af adgangskode vil generere en ny API nøgle
|
||||||
profile.opml_export=OPML eksport
|
profile.opml_export=OPML eksport
|
||||||
profile.delete_account=Slet konto
|
profile.delete_account=Slet konto
|
||||||
|
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
|
||||||
|
|
||||||
about.rest_api=REST API
|
about.rest_api=REST API
|
||||||
about.keyboard_shortcuts=Tastaturgenveje
|
about.keyboard_shortcuts=Tastaturgenveje
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ global.download=Herunterladen
|
|||||||
global.link=Link
|
global.link=Link
|
||||||
global.bookmark=Lesezeichen
|
global.bookmark=Lesezeichen
|
||||||
global.close=Schließen
|
global.close=Schließen
|
||||||
|
global.tags=Tags
|
||||||
|
|
||||||
tree.subscribe=Abonnieren
|
tree.subscribe=Abonnieren
|
||||||
tree.import=Importieren
|
tree.import=Importieren
|
||||||
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Nach Datum sortieren (auf-/absteigend)
|
|||||||
toolbar.titles_only=Nur Überschriften
|
toolbar.titles_only=Nur Überschriften
|
||||||
toolbar.expanded_view=Ausgedehnte Ansicht
|
toolbar.expanded_view=Ausgedehnte Ansicht
|
||||||
toolbar.mark_all_as_read=Alle Artikel als gelesen markieren
|
toolbar.mark_all_as_read=Alle Artikel als gelesen markieren
|
||||||
|
toolbar.mark_all_older_12_hours=Artikel älter als 12 Stunden
|
||||||
toolbar.mark_all_older_day=Artikel älter als ein Tag
|
toolbar.mark_all_older_day=Artikel älter als ein Tag
|
||||||
toolbar.mark_all_older_week=Artikel älter als eine Woche
|
toolbar.mark_all_older_week=Artikel älter als eine Woche
|
||||||
toolbar.mark_all_older_two_weeks=Artikel älter als zwei Wochen
|
toolbar.mark_all_older_two_weeks=Artikel älter als zwei Wochen
|
||||||
@@ -66,6 +68,8 @@ settings.general.show_unread=Zeige Feeds und Kategorien mit ungelesenen Einträg
|
|||||||
settings.general.social_buttons=Zeige Buttons zum Teilen von Inhalten über soziale Netzwerke
|
settings.general.social_buttons=Zeige Buttons zum Teilen von Inhalten über soziale Netzwerke
|
||||||
settings.general.scroll_marks=In der ausgedehnten Ansicht werden Artikel beim Scrollen als gelesen markiert
|
settings.general.scroll_marks=In der ausgedehnten Ansicht werden Artikel beim Scrollen als gelesen markiert
|
||||||
settings.appearance=Aussehen
|
settings.appearance=Aussehen
|
||||||
|
settings.scroll_speed=Geschwindigkeit beim scrollen zwischen Einträgen (in Millisekunden)
|
||||||
|
settings.scroll_speed.help=setze auf 0 zum deaktivieren
|
||||||
settings.theme=Theme
|
settings.theme=Theme
|
||||||
settings.submit_your_theme=Füg dein Theme hinzu
|
settings.submit_your_theme=Füg dein Theme hinzu
|
||||||
settings.custom_css=Eigenes CSS
|
settings.custom_css=Eigenes CSS
|
||||||
@@ -77,13 +81,16 @@ details.name=Name
|
|||||||
details.category=Kategorie
|
details.category=Kategorie
|
||||||
details.position=Position
|
details.position=Position
|
||||||
details.last_refresh=Letzte Aktualisierung
|
details.last_refresh=Letzte Aktualisierung
|
||||||
details.message=Last refresh message ####### Needs translation
|
details.message=Nachricht der letzten Aktualisierung
|
||||||
details.next_refresh=Nächste Aktualisierung
|
details.next_refresh=Nächste Aktualisierung
|
||||||
details.queued_for_refresh=Wartet auf Aktualisierung
|
details.queued_for_refresh=Wartet auf Aktualisierung
|
||||||
details.feed_url=Feed Adresse
|
details.feed_url=Feed Adresse
|
||||||
details.generate_api_key_first=Generiere zuerst einen API Schlüssel in deinem Profil.
|
details.generate_api_key_first=Generiere zuerst einen API Schlüssel in deinem Profil.
|
||||||
details.unsubscribe=Kündigen
|
details.unsubscribe=Kündigen
|
||||||
|
details.unsubscribe_confirmation=Bist du sicher das du diesen Feed kündigen möchtest?
|
||||||
|
details.delete_category_confirmation=Bist du sicher das du diese Kategorie löschen möchtest?
|
||||||
details.category_details=Kategoriedetails
|
details.category_details=Kategoriedetails
|
||||||
|
details.tag_details=Tag Details
|
||||||
details.parent_category=Übergeordnete Kategorie
|
details.parent_category=Übergeordnete Kategorie
|
||||||
|
|
||||||
profile.user_name=Benutzername
|
profile.user_name=Benutzername
|
||||||
@@ -97,7 +104,8 @@ profile.api_key_not_generated=Noch nicht generiert
|
|||||||
profile.generate_new_api_key=Generiere einen neuen API key
|
profile.generate_new_api_key=Generiere einen neuen API key
|
||||||
profile.generate_new_api_key_info=Das Ändern des Passwortes erzeugt einen neuen API Schlüssel
|
profile.generate_new_api_key_info=Das Ändern des Passwortes erzeugt einen neuen API Schlüssel
|
||||||
profile.opml_export=OPML exportieren
|
profile.opml_export=OPML exportieren
|
||||||
profile.delete_account=Lösche den Account
|
profile.delete_account=Account löschen
|
||||||
|
profile.delete_account_confirmation=Deinen Account löschen? Es gibt kein Zurück!
|
||||||
|
|
||||||
about.rest_api=REST API
|
about.rest_api=REST API
|
||||||
about.keyboard_shortcuts=Tastatur Kurzbefehle
|
about.keyboard_shortcuts=Tastatur Kurzbefehle
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ global.download=Download
|
|||||||
global.link=Link
|
global.link=Link
|
||||||
global.bookmark=Bookmark
|
global.bookmark=Bookmark
|
||||||
global.close=Close
|
global.close=Close
|
||||||
|
global.tags=Tags
|
||||||
|
|
||||||
tree.subscribe=Subscribe
|
tree.subscribe=Subscribe
|
||||||
tree.import=Import
|
tree.import=Import
|
||||||
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sort by date asc/desc
|
|||||||
toolbar.titles_only=Titles only
|
toolbar.titles_only=Titles only
|
||||||
toolbar.expanded_view=Expanded view
|
toolbar.expanded_view=Expanded view
|
||||||
toolbar.mark_all_as_read=Mark all as read
|
toolbar.mark_all_as_read=Mark all as read
|
||||||
|
toolbar.mark_all_older_12_hours=Items older than 12 hours
|
||||||
toolbar.mark_all_older_day=Items older than a day
|
toolbar.mark_all_older_day=Items older than a day
|
||||||
toolbar.mark_all_older_week=Items older than a week
|
toolbar.mark_all_older_week=Items older than a week
|
||||||
toolbar.mark_all_older_two_weeks=Items older than two weeks
|
toolbar.mark_all_older_two_weeks=Items older than two weeks
|
||||||
@@ -66,6 +68,8 @@ settings.general.show_unread=Show feeds and categories with no unread entries
|
|||||||
settings.general.social_buttons=Show social sharing buttons
|
settings.general.social_buttons=Show social sharing buttons
|
||||||
settings.general.scroll_marks=In expanded view, scrolling through entries mark them as read
|
settings.general.scroll_marks=In expanded view, scrolling through entries mark them as read
|
||||||
settings.appearance=Appearance
|
settings.appearance=Appearance
|
||||||
|
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds)
|
||||||
|
settings.scroll_speed.help=set to 0 to disable
|
||||||
settings.theme=Theme
|
settings.theme=Theme
|
||||||
settings.submit_your_theme=Submit your theme
|
settings.submit_your_theme=Submit your theme
|
||||||
settings.custom_css=Custom CSS
|
settings.custom_css=Custom CSS
|
||||||
@@ -83,7 +87,10 @@ details.queued_for_refresh=Queued for refresh
|
|||||||
details.feed_url=Feed URL
|
details.feed_url=Feed URL
|
||||||
details.generate_api_key_first=Generate an API key in your profile first.
|
details.generate_api_key_first=Generate an API key in your profile first.
|
||||||
details.unsubscribe=Unsubscribe
|
details.unsubscribe=Unsubscribe
|
||||||
|
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed?
|
||||||
|
details.delete_category_confirmation=Are you sure you want to delete this category?
|
||||||
details.category_details=Category details
|
details.category_details=Category details
|
||||||
|
details.tag_details=Tag details
|
||||||
details.parent_category=Parent category
|
details.parent_category=Parent category
|
||||||
|
|
||||||
profile.user_name=User name
|
profile.user_name=User name
|
||||||
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Generate new API key
|
|||||||
profile.generate_new_api_key_info=Changing password will generate a new API key
|
profile.generate_new_api_key_info=Changing password will generate a new API key
|
||||||
profile.opml_export=OPML export
|
profile.opml_export=OPML export
|
||||||
profile.delete_account=Delete account
|
profile.delete_account=Delete account
|
||||||
|
profile.delete_account_confirmation=Delete your account? There's no turning back!
|
||||||
|
|
||||||
about.rest_api=REST API
|
about.rest_api=REST API
|
||||||
about.keyboard_shortcuts=Keyboard shortcuts
|
about.keyboard_shortcuts=Keyboard shortcuts
|
||||||
@@ -145,4 +153,4 @@ about.shortcuts.fullscreen=toggle full screen mode
|
|||||||
about.shortcuts.font_size=increase/decrease font size of the current entry
|
about.shortcuts.font_size=increase/decrease font size of the current entry
|
||||||
about.shortcuts.go_to_all=go to the All view
|
about.shortcuts.go_to_all=go to the All view
|
||||||
about.shortcuts.go_to_starred=go to the Starred view
|
about.shortcuts.go_to_starred=go to the Starred view
|
||||||
about.shortcuts.feed_search=navigate to a subscription by entering the subscription name
|
about.shortcuts.feed_search=navigate to a subscription by entering the subscription name
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ global.download=Descargar
|
|||||||
global.link=Enlace
|
global.link=Enlace
|
||||||
global.bookmark=Marcador
|
global.bookmark=Marcador
|
||||||
global.close=Close ####### Needs translation
|
global.close=Close ####### Needs translation
|
||||||
|
global.tags=Tags ####### Needs translation
|
||||||
|
|
||||||
tree.subscribe=Subscribir
|
tree.subscribe=Subscribir
|
||||||
tree.import=Importar
|
tree.import=Importar
|
||||||
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Ordenar por fecha asc/desc
|
|||||||
toolbar.titles_only=Sólo Títulos
|
toolbar.titles_only=Sólo Títulos
|
||||||
toolbar.expanded_view=Vista Expandida
|
toolbar.expanded_view=Vista Expandida
|
||||||
toolbar.mark_all_as_read=Marcar todos como leído
|
toolbar.mark_all_as_read=Marcar todos como leído
|
||||||
|
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
|
||||||
toolbar.mark_all_older_day=Artículos anteriores a un día
|
toolbar.mark_all_older_day=Artículos anteriores a un día
|
||||||
toolbar.mark_all_older_week=Artículos más de una semana
|
toolbar.mark_all_older_week=Artículos más de una semana
|
||||||
toolbar.mark_all_older_two_weeks=Artículos más de does semanas
|
toolbar.mark_all_older_two_weeks=Artículos más de does semanas
|
||||||
@@ -66,6 +68,8 @@ settings.general.show_unread=Mostrar canales y categorías sin entradas no leíd
|
|||||||
settings.general.social_buttons=Mostrar botones de compartir de redes sociales.
|
settings.general.social_buttons=Mostrar botones de compartir de redes sociales.
|
||||||
settings.general.scroll_marks=En vista expandida, el desplazamiento por las entradas las marca como leídas
|
settings.general.scroll_marks=En vista expandida, el desplazamiento por las entradas las marca como leídas
|
||||||
settings.appearance=Appearance ####### Needs translation
|
settings.appearance=Appearance ####### Needs translation
|
||||||
|
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
|
||||||
|
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
|
||||||
settings.theme=Theme ####### Needs translation
|
settings.theme=Theme ####### Needs translation
|
||||||
settings.submit_your_theme=Submit your theme ####### Needs translation
|
settings.submit_your_theme=Submit your theme ####### Needs translation
|
||||||
settings.custom_css=CSS Personalizado
|
settings.custom_css=CSS Personalizado
|
||||||
@@ -83,7 +87,10 @@ details.queued_for_refresh=Queued for refresh ####### Needs translation
|
|||||||
details.feed_url=URL del Canal
|
details.feed_url=URL del Canal
|
||||||
details.generate_api_key_first=Genera una llave API en tu perfil primero.
|
details.generate_api_key_first=Genera una llave API en tu perfil primero.
|
||||||
details.unsubscribe=Terminar subscripción
|
details.unsubscribe=Terminar subscripción
|
||||||
|
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
|
||||||
|
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
|
||||||
details.category_details=Detalles de la categoría
|
details.category_details=Detalles de la categoría
|
||||||
|
details.tag_details=Tag details ####### Needs translation
|
||||||
details.parent_category=Categoría principal
|
details.parent_category=Categoría principal
|
||||||
|
|
||||||
profile.user_name=Nombre de usuario
|
profile.user_name=Nombre de usuario
|
||||||
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Generar nueva llave API
|
|||||||
profile.generate_new_api_key_info=Al cambiar la contraseña se generará una nueva llave API
|
profile.generate_new_api_key_info=Al cambiar la contraseña se generará una nueva llave API
|
||||||
profile.opml_export=Exportación de OPML
|
profile.opml_export=Exportación de OPML
|
||||||
profile.delete_account=Eliminar cuenta
|
profile.delete_account=Eliminar cuenta
|
||||||
|
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
|
||||||
|
|
||||||
about.rest_api=REST API
|
about.rest_api=REST API
|
||||||
about.keyboard_shortcuts=Atajos de teclado
|
about.keyboard_shortcuts=Atajos de teclado
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ global.download=بارگیری
|
|||||||
global.link=پیوند
|
global.link=پیوند
|
||||||
global.bookmark=بوکمارک
|
global.bookmark=بوکمارک
|
||||||
global.close=بستن
|
global.close=بستن
|
||||||
|
global.tags=برجسپها
|
||||||
|
|
||||||
tree.subscribe=مشترک شوید
|
tree.subscribe=مشترک شوید
|
||||||
tree.import=درونریزی
|
tree.import=درونریزی
|
||||||
@@ -31,11 +32,12 @@ toolbar.all=همه
|
|||||||
toolbar.previous_entry=مطلب قبلی
|
toolbar.previous_entry=مطلب قبلی
|
||||||
toolbar.next_entry=مطلب بعدی
|
toolbar.next_entry=مطلب بعدی
|
||||||
toolbar.refresh=تازهسازی
|
toolbar.refresh=تازهسازی
|
||||||
toolbar.refresh_all=Force refresh all my feeds ####### Needs translation
|
toolbar.refresh_all=مجبورکردن تازهسازی همهٔ خوراکها
|
||||||
toolbar.sort_by_asc_desc=مرتبکردن بر اساس تاریخ بهصورت صعودی/نزولی
|
toolbar.sort_by_asc_desc=مرتبکردن بر اساس تاریخ بهصورت صعودی/نزولی
|
||||||
toolbar.titles_only=فقط عنوانها
|
toolbar.titles_only=فقط عنوانها
|
||||||
toolbar.expanded_view=نمای گسترشیافته
|
toolbar.expanded_view=نمای گسترشیافته
|
||||||
toolbar.mark_all_as_read=علامتگذاری تمامی مطالب بهعنوان خواندهشده
|
toolbar.mark_all_as_read=علامتگذاری تمامی مطالب بهعنوان خواندهشده
|
||||||
|
toolbar.mark_all_older_12_hours=مطالب قدیمیتر از ۱۲ ساعت
|
||||||
toolbar.mark_all_older_day=مطالب قدیمیتر از یک روز
|
toolbar.mark_all_older_day=مطالب قدیمیتر از یک روز
|
||||||
toolbar.mark_all_older_week=مطالب قدیمیتر از یک هفته
|
toolbar.mark_all_older_week=مطالب قدیمیتر از یک هفته
|
||||||
toolbar.mark_all_older_two_weeks=مطالب قدیمی تر از چند هفته قیل
|
toolbar.mark_all_older_two_weeks=مطالب قدیمی تر از چند هفته قیل
|
||||||
@@ -52,8 +54,8 @@ view.error_while_loading_feed=متأسفانه، هنگام بارگیری ای
|
|||||||
view.keep_unread=خواندهنشده نگهدار
|
view.keep_unread=خواندهنشده نگهدار
|
||||||
view.no_unread_items=هیچ مطلب خواندهنشدهای ندارد.
|
view.no_unread_items=هیچ مطلب خواندهنشدهای ندارد.
|
||||||
view.mark_up_to_here=تا اینجا را خواندهشده در نظر بگیر
|
view.mark_up_to_here=تا اینجا را خواندهشده در نظر بگیر
|
||||||
view.search_for=searching for: ####### Needs translation
|
view.search_for=جستجو برای:
|
||||||
view.no_search_results=No match found for the requested keywords ####### Needs translation
|
view.no_search_results=هیج نتیجهای برای کلیدواژههای درخواستی یافت نشد
|
||||||
|
|
||||||
feedsearch.hint=نوشتن بر روی یک اشتراک...
|
feedsearch.hint=نوشتن بر روی یک اشتراک...
|
||||||
feedsearch.help=دکمهٔ بازگشت برای انتخاب و دکمههای جهتدار را برای ناوبری استفاده کن.
|
feedsearch.help=دکمهٔ بازگشت برای انتخاب و دکمههای جهتدار را برای ناوبری استفاده کن.
|
||||||
@@ -66,6 +68,8 @@ settings.general.show_unread=تنها خوراکها و دستههای ر
|
|||||||
settings.general.social_buttons=نشاندادن دکمههای اشتراکگذاری در شبکههای اجتماعی
|
settings.general.social_buttons=نشاندادن دکمههای اشتراکگذاری در شبکههای اجتماعی
|
||||||
settings.general.scroll_marks=در نمای گسترشیافته، لغزیدن بر روی مطالب بهعنوان نشانهگذاری بهعنوان خواندهشده در نظر گرفتهشوند.
|
settings.general.scroll_marks=در نمای گسترشیافته، لغزیدن بر روی مطالب بهعنوان نشانهگذاری بهعنوان خواندهشده در نظر گرفتهشوند.
|
||||||
settings.appearance=ظاهر
|
settings.appearance=ظاهر
|
||||||
|
settings.scroll_speed=سرعت لغزش هنگام گشتن بین مدخلها (به میلیثانیه)
|
||||||
|
settings.scroll_speed.help=قراردادن به ۰ برای غیرفعالکردن
|
||||||
settings.theme=پوسته
|
settings.theme=پوسته
|
||||||
settings.submit_your_theme=پوستهٔ خود را ارسالکنید
|
settings.submit_your_theme=پوستهٔ خود را ارسالکنید
|
||||||
settings.custom_css=سیاساس شخصیسازیشده
|
settings.custom_css=سیاساس شخصیسازیشده
|
||||||
@@ -77,13 +81,16 @@ details.name=نام
|
|||||||
details.category=دسته
|
details.category=دسته
|
||||||
details.position=موقعیت
|
details.position=موقعیت
|
||||||
details.last_refresh=آخرین بروزرسانی
|
details.last_refresh=آخرین بروزرسانی
|
||||||
details.message=Last refresh message ####### Needs translation
|
details.message=پیام آخرین تازهسازی
|
||||||
details.next_refresh=بروزرسانی بعدی
|
details.next_refresh=بروزرسانی بعدی
|
||||||
details.queued_for_refresh=منتظر برای بروزرسانی
|
details.queued_for_refresh=منتظر برای بروزرسانی
|
||||||
details.feed_url=نشانی خوراک
|
details.feed_url=نشانی خوراک
|
||||||
details.generate_api_key_first=ابتدا یک کلید API در نمایهٔ خود ایجاد کنید.
|
details.generate_api_key_first=ابتدا یک کلید API در نمایهٔ خود ایجاد کنید.
|
||||||
details.unsubscribe=لغو اشتراک
|
details.unsubscribe=لغو اشتراک
|
||||||
|
details.unsubscribe_confirmation=مطمئنید میخواهید از این این لغو اشتراک کنید؟
|
||||||
|
details.delete_category_confirmation=مطمئنید میخواهید این رده را حذف کنید؟
|
||||||
details.category_details=جزئیات دسته
|
details.category_details=جزئیات دسته
|
||||||
|
details.tag_details=جزئیات برچسپ
|
||||||
details.parent_category=ردهٔ پدر
|
details.parent_category=ردهٔ پدر
|
||||||
|
|
||||||
profile.user_name=نام کاربری
|
profile.user_name=نام کاربری
|
||||||
@@ -98,10 +105,11 @@ profile.generate_new_api_key=ایجاد کلید جدید API
|
|||||||
profile.generate_new_api_key_info=تغییر گذرواژه کلید API بهوجود خواهد آورد.
|
profile.generate_new_api_key_info=تغییر گذرواژه کلید API بهوجود خواهد آورد.
|
||||||
profile.opml_export=خارجسازی OPML
|
profile.opml_export=خارجسازی OPML
|
||||||
profile.delete_account=حذف حساب کاربری
|
profile.delete_account=حذف حساب کاربری
|
||||||
|
profile.delete_account_confirmation=حذف حسابتان؟ بازگشتی وجود ندارد!
|
||||||
|
|
||||||
about.rest_api=REST API
|
about.rest_api=REST API
|
||||||
about.keyboard_shortcuts=کلیدهای میانبر
|
about.keyboard_shortcuts=کلیدهای میانبر
|
||||||
about.version=CommaFeed version ####### Needs translation
|
about.version=نسخهٔ کامافید
|
||||||
about.line1_prefix=کامافید یک پروژه متنباز است. مخازن آن در
|
about.line1_prefix=کامافید یک پروژه متنباز است. مخازن آن در
|
||||||
about.line1_suffix=میزبانی میشود.
|
about.line1_suffix=میزبانی میشود.
|
||||||
about.line2_prefix=اگر شما به مسئلهای برخورده اید، لطفاً آن را در صفحه مسائل گزارش دهید
|
about.line2_prefix=اگر شما به مسئلهای برخورده اید، لطفاً آن را در صفحه مسائل گزارش دهید
|
||||||
@@ -143,7 +151,7 @@ about.shortcuts.mark_all_as_read=علامتگذاری تمامی مطالب
|
|||||||
about.shortcuts.open_in_new_tab_mark_as_read=بازکردن مطلب در سربرگ جدید و علامتگذاری آن بهعنوان خواندهشده
|
about.shortcuts.open_in_new_tab_mark_as_read=بازکردن مطلب در سربرگ جدید و علامتگذاری آن بهعنوان خواندهشده
|
||||||
about.shortcuts.fullscreen=فعال/غیرفعالکردن حالت تمام صفحه
|
about.shortcuts.fullscreen=فعال/غیرفعالکردن حالت تمام صفحه
|
||||||
about.shortcuts.font_size=افزایش/کاهش اندازهٔ قلم مدخل فعلی
|
about.shortcuts.font_size=افزایش/کاهش اندازهٔ قلم مدخل فعلی
|
||||||
about.shortcuts.go_to_all=go to the All view ####### Needs translation
|
about.shortcuts.go_to_all=رفتن به نمای همه
|
||||||
about.shortcuts.go_to_starred=go to the Starred view ####### Needs translation
|
about.shortcuts.go_to_starred=رفتن به نمای ستاره دادهشدهها
|
||||||
about.shortcuts.feed_search=ناوبری به یک اشتراک با نوشتن نام اشتراک
|
about.shortcuts.feed_search=ناوبری به یک اشتراک با نوشتن نام اشتراک
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ global.download=Lataa
|
|||||||
global.link=Linkki
|
global.link=Linkki
|
||||||
global.bookmark=Kirjanmerkki
|
global.bookmark=Kirjanmerkki
|
||||||
global.close=Sulje
|
global.close=Sulje
|
||||||
|
global.tags=Tagit
|
||||||
|
|
||||||
tree.subscribe=Tilaa syöte
|
tree.subscribe=Tilaa syöte
|
||||||
tree.import=Tuo
|
tree.import=Tuo
|
||||||
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Järjestä päivämäärän mukaan nousevasti/laskevast
|
|||||||
toolbar.titles_only=Näytä vain otsikot
|
toolbar.titles_only=Näytä vain otsikot
|
||||||
toolbar.expanded_view=Laajennettu näkymä
|
toolbar.expanded_view=Laajennettu näkymä
|
||||||
toolbar.mark_all_as_read=Merkitse kaikki luetuiksi
|
toolbar.mark_all_as_read=Merkitse kaikki luetuiksi
|
||||||
|
toolbar.mark_all_older_12_hours=12 tuntia vanhemmat otsikot
|
||||||
toolbar.mark_all_older_day=Päivää vanhemmat otsikot
|
toolbar.mark_all_older_day=Päivää vanhemmat otsikot
|
||||||
toolbar.mark_all_older_week=Viikkoa vanhemmat otsikot
|
toolbar.mark_all_older_week=Viikkoa vanhemmat otsikot
|
||||||
toolbar.mark_all_older_two_weeks=Kahta viikkoa vanhemmat otsikot
|
toolbar.mark_all_older_two_weeks=Kahta viikkoa vanhemmat otsikot
|
||||||
@@ -52,8 +54,8 @@ view.error_while_loading_feed=Virhe tilausta ladattaessa
|
|||||||
view.keep_unread=Pidä lukemattomana
|
view.keep_unread=Pidä lukemattomana
|
||||||
view.no_unread_items=ei sisällä lukemattomia otsikoita.
|
view.no_unread_items=ei sisällä lukemattomia otsikoita.
|
||||||
view.mark_up_to_here=Merkitse luetuksi tähän asti
|
view.mark_up_to_here=Merkitse luetuksi tähän asti
|
||||||
view.search_for=searching for: ####### Needs translation
|
view.search_for=Etsi sanoilla:
|
||||||
view.no_search_results=No match found for the requested keywords ####### Needs translation
|
view.no_search_results=Ei tuloksia annetuilla hakusanoilla.
|
||||||
|
|
||||||
feedsearch.hint=Kirjoita syötteen nimi...
|
feedsearch.hint=Kirjoita syötteen nimi...
|
||||||
feedsearch.help=Siirry syötteiden välillä nuolinäppäimillä ja valitse syöte enterillä.
|
feedsearch.help=Siirry syötteiden välillä nuolinäppäimillä ja valitse syöte enterillä.
|
||||||
@@ -66,6 +68,8 @@ settings.general.show_unread=Näytä syötteet ja kansiot, joissa ei ole lukemat
|
|||||||
settings.general.social_buttons=Näytä jakonapit
|
settings.general.social_buttons=Näytä jakonapit
|
||||||
settings.general.scroll_marks=Laajennetussa näkymässä otsikoiden selaaminen merkitsee ne luetuiksi
|
settings.general.scroll_marks=Laajennetussa näkymässä otsikoiden selaaminen merkitsee ne luetuiksi
|
||||||
settings.appearance=Ulkonäkö
|
settings.appearance=Ulkonäkö
|
||||||
|
settings.scroll_speed=Vieritysnopeus otsikoiden välillä navigoidessa (millisekunneissa)
|
||||||
|
settings.scroll_speed.help=Aseta 0 poistaaksesi vieritys käytöstä.
|
||||||
settings.theme=Teema
|
settings.theme=Teema
|
||||||
settings.submit_your_theme=Lähetä oma teemasi
|
settings.submit_your_theme=Lähetä oma teemasi
|
||||||
settings.custom_css=Oma CSS
|
settings.custom_css=Oma CSS
|
||||||
@@ -77,13 +81,16 @@ details.name=Nimi
|
|||||||
details.category=Kansio
|
details.category=Kansio
|
||||||
details.position=Paikka
|
details.position=Paikka
|
||||||
details.last_refresh=Viimeisin päivitys
|
details.last_refresh=Viimeisin päivitys
|
||||||
details.message=Last refresh message ####### Needs translation
|
details.message=Viimeisimmän päivityksen viesti
|
||||||
details.next_refresh=Seuraava päivitys
|
details.next_refresh=Seuraava päivitys
|
||||||
details.queued_for_refresh=Jonossa päivitettäväksi
|
details.queued_for_refresh=Jonossa päivitettäväksi
|
||||||
details.feed_url=Syötteen osoite
|
details.feed_url=Syötteen osoite
|
||||||
details.generate_api_key_first=Luo API-avain profiilissasi.
|
details.generate_api_key_first=Luo API-avain profiilissasi.
|
||||||
details.unsubscribe=Peruuta tilaus
|
details.unsubscribe=Peruuta tilaus
|
||||||
|
details.unsubscribe_confirmation=Haluatko varmasti lopettaa tämän syötteen tilauksen?
|
||||||
|
details.delete_category_confirmation=Haluatko varmasti poistaa tämän kansion?
|
||||||
details.category_details=Kansion tiedot
|
details.category_details=Kansion tiedot
|
||||||
|
details.tag_details=Tagin tiedot
|
||||||
details.parent_category=Yläkansio
|
details.parent_category=Yläkansio
|
||||||
|
|
||||||
profile.user_name=Käyttäjänimi
|
profile.user_name=Käyttäjänimi
|
||||||
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Luo uusi API-avain
|
|||||||
profile.generate_new_api_key_info=Salasanan vaihtaminen luo uuden API-avaimen
|
profile.generate_new_api_key_info=Salasanan vaihtaminen luo uuden API-avaimen
|
||||||
profile.opml_export=OPML vienti
|
profile.opml_export=OPML vienti
|
||||||
profile.delete_account=Poista tunnus
|
profile.delete_account=Poista tunnus
|
||||||
|
profile.delete_account_confirmation=Haluatko varmasti poistaa tunnuksesi? Tätä ei voi perua!
|
||||||
|
|
||||||
about.rest_api=REST-API
|
about.rest_api=REST-API
|
||||||
about.keyboard_shortcuts=Näppäinoikotiet
|
about.keyboard_shortcuts=Näppäinoikotiet
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ global.download=Télécharger
|
|||||||
global.link=Lien
|
global.link=Lien
|
||||||
global.bookmark=Favoris
|
global.bookmark=Favoris
|
||||||
global.close=Fermer
|
global.close=Fermer
|
||||||
|
global.tags=Tags ####### Needs translation
|
||||||
|
|
||||||
tree.subscribe=S'abonner
|
tree.subscribe=S'abonner
|
||||||
tree.import=Importer
|
tree.import=Importer
|
||||||
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Trier par date croissante/décroissante
|
|||||||
toolbar.titles_only=Titres uniquement
|
toolbar.titles_only=Titres uniquement
|
||||||
toolbar.expanded_view=Vue étendue
|
toolbar.expanded_view=Vue étendue
|
||||||
toolbar.mark_all_as_read=Tout marquer comme lu
|
toolbar.mark_all_as_read=Tout marquer comme lu
|
||||||
|
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
|
||||||
toolbar.mark_all_older_day=Articles de plus d'un jour
|
toolbar.mark_all_older_day=Articles de plus d'un jour
|
||||||
toolbar.mark_all_older_week=Articles de plus d'une semaine
|
toolbar.mark_all_older_week=Articles de plus d'une semaine
|
||||||
toolbar.mark_all_older_two_weeks=Articles de plus d'un mois
|
toolbar.mark_all_older_two_weeks=Articles de plus d'un mois
|
||||||
@@ -66,6 +68,8 @@ settings.general.show_unread=Afficher les flux et les catégories pour lesquels
|
|||||||
settings.general.social_buttons=Afficher les boutons de partage sur réseaux sociaux
|
settings.general.social_buttons=Afficher les boutons de partage sur réseaux sociaux
|
||||||
settings.general.scroll_marks=En mode de lecture étendu, marquer comme lu les éléments lorsque la fenêtre descend.
|
settings.general.scroll_marks=En mode de lecture étendu, marquer comme lu les éléments lorsque la fenêtre descend.
|
||||||
settings.appearance=Apparence
|
settings.appearance=Apparence
|
||||||
|
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
|
||||||
|
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
|
||||||
settings.theme=Thème
|
settings.theme=Thème
|
||||||
settings.submit_your_theme=Soumettez votre thème.
|
settings.submit_your_theme=Soumettez votre thème.
|
||||||
settings.custom_css=CSS personnelle
|
settings.custom_css=CSS personnelle
|
||||||
@@ -83,7 +87,10 @@ details.queued_for_refresh=En file d'attente
|
|||||||
details.feed_url=URL du flux
|
details.feed_url=URL du flux
|
||||||
details.generate_api_key_first=Générez une clé API dans votre profil d'abord.
|
details.generate_api_key_first=Générez une clé API dans votre profil d'abord.
|
||||||
details.unsubscribe=Se désabonner
|
details.unsubscribe=Se désabonner
|
||||||
|
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
|
||||||
|
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
|
||||||
details.category_details=Détails de la catégorie
|
details.category_details=Détails de la catégorie
|
||||||
|
details.tag_details=Tag details ####### Needs translation
|
||||||
details.parent_category=Catégorie parente
|
details.parent_category=Catégorie parente
|
||||||
|
|
||||||
profile.user_name=Nom
|
profile.user_name=Nom
|
||||||
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Générer une nouvelle clé API
|
|||||||
profile.generate_new_api_key_info=Changer de mot de passe va générer une nouvelle clé API
|
profile.generate_new_api_key_info=Changer de mot de passe va générer une nouvelle clé API
|
||||||
profile.opml_export=Export du fichier OPML
|
profile.opml_export=Export du fichier OPML
|
||||||
profile.delete_account=Effacer le compte
|
profile.delete_account=Effacer le compte
|
||||||
|
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
|
||||||
|
|
||||||
about.rest_api=API REST
|
about.rest_api=API REST
|
||||||
about.keyboard_shortcuts=Raccourcis clavier
|
about.keyboard_shortcuts=Raccourcis clavier
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ global.download=Descargar
|
|||||||
global.link=Ligazón
|
global.link=Ligazón
|
||||||
global.bookmark=Marcador
|
global.bookmark=Marcador
|
||||||
global.close=Pechar
|
global.close=Pechar
|
||||||
|
global.tags=Tags ####### Needs translation
|
||||||
|
|
||||||
tree.subscribe=Subscribir
|
tree.subscribe=Subscribir
|
||||||
tree.import=Importar
|
tree.import=Importar
|
||||||
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Ordenar por data asc/desc
|
|||||||
toolbar.titles_only=Só títulos
|
toolbar.titles_only=Só títulos
|
||||||
toolbar.expanded_view=Vista expandida
|
toolbar.expanded_view=Vista expandida
|
||||||
toolbar.mark_all_as_read=Marcar todos como lidos
|
toolbar.mark_all_as_read=Marcar todos como lidos
|
||||||
|
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
|
||||||
toolbar.mark_all_older_day=Artigos anteriores a un día
|
toolbar.mark_all_older_day=Artigos anteriores a un día
|
||||||
toolbar.mark_all_older_week=Artigos de máis de unha semana
|
toolbar.mark_all_older_week=Artigos de máis de unha semana
|
||||||
toolbar.mark_all_older_two_weeks=Artigos de máis de dúas semanas
|
toolbar.mark_all_older_two_weeks=Artigos de máis de dúas semanas
|
||||||
@@ -66,6 +68,8 @@ settings.general.show_unread=Mostrar fontes e categorías sen entradas non lidas
|
|||||||
settings.general.social_buttons=Mostrar botóns de compartir en redes sociais.
|
settings.general.social_buttons=Mostrar botóns de compartir en redes sociais.
|
||||||
settings.general.scroll_marks=En vista expandida, o desplazamento polas entradas márcaas como lidas.
|
settings.general.scroll_marks=En vista expandida, o desplazamento polas entradas márcaas como lidas.
|
||||||
settings.appearance=Aspecto
|
settings.appearance=Aspecto
|
||||||
|
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
|
||||||
|
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
|
||||||
settings.theme=Decorado
|
settings.theme=Decorado
|
||||||
settings.submit_your_theme=Envíe o seu decorado
|
settings.submit_your_theme=Envíe o seu decorado
|
||||||
settings.custom_css=CSS Personalizado
|
settings.custom_css=CSS Personalizado
|
||||||
@@ -83,7 +87,10 @@ details.queued_for_refresh=En cola para actualizar
|
|||||||
details.feed_url=URL da fonte
|
details.feed_url=URL da fonte
|
||||||
details.generate_api_key_first=Antes debes xerar unha chave API no teu perfil.
|
details.generate_api_key_first=Antes debes xerar unha chave API no teu perfil.
|
||||||
details.unsubscribe=Rematar suscripción
|
details.unsubscribe=Rematar suscripción
|
||||||
|
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
|
||||||
|
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
|
||||||
details.category_details=Detalles da categoría
|
details.category_details=Detalles da categoría
|
||||||
|
details.tag_details=Tag details ####### Needs translation
|
||||||
details.parent_category=Categoría principal
|
details.parent_category=Categoría principal
|
||||||
|
|
||||||
profile.user_name=Nome de usuario
|
profile.user_name=Nome de usuario
|
||||||
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Xerar nova chave da API
|
|||||||
profile.generate_new_api_key_info=Ao cambiar o contrasinal xerarase unha nova chave API
|
profile.generate_new_api_key_info=Ao cambiar o contrasinal xerarase unha nova chave API
|
||||||
profile.opml_export=Exportación de OPML
|
profile.opml_export=Exportación de OPML
|
||||||
profile.delete_account=Eliminar conta
|
profile.delete_account=Eliminar conta
|
||||||
|
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
|
||||||
|
|
||||||
about.rest_api=REST API
|
about.rest_api=REST API
|
||||||
about.keyboard_shortcuts=Atallos de teclado
|
about.keyboard_shortcuts=Atallos de teclado
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ global.download=جیرأکش
|
|||||||
global.link=خال
|
global.link=خال
|
||||||
global.bookmark=بوکمارک
|
global.bookmark=بوکمارک
|
||||||
global.close=دَوَستن
|
global.close=دَوَستن
|
||||||
|
global.tags=Tags ####### Needs translation
|
||||||
|
|
||||||
tree.subscribe=مشترک ببید
|
tree.subscribe=مشترک ببید
|
||||||
tree.import=درینأدأن
|
tree.import=درینأدأن
|
||||||
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=تاریخˇ سر دچئن
|
|||||||
toolbar.titles_only=خالی تیتران
|
toolbar.titles_only=خالی تیتران
|
||||||
toolbar.expanded_view=واشاده نما
|
toolbar.expanded_view=واشاده نما
|
||||||
toolbar.mark_all_as_read=همهته مطالبه چاکون بخانده
|
toolbar.mark_all_as_read=همهته مطالبه چاکون بخانده
|
||||||
|
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
|
||||||
toolbar.mark_all_older_day=یک روز پیشترˇ مطالب
|
toolbar.mark_all_older_day=یک روز پیشترˇ مطالب
|
||||||
toolbar.mark_all_older_week=یک هفته پیشترˇ مطالب
|
toolbar.mark_all_older_week=یک هفته پیشترˇ مطالب
|
||||||
toolbar.mark_all_older_two_weeks=چن هفته پیشترˇ مطالب
|
toolbar.mark_all_older_two_weeks=چن هفته پیشترˇ مطالب
|
||||||
@@ -66,6 +68,8 @@ settings.general.show_unread=تنها خوراکها و دستههای ر
|
|||||||
settings.general.social_buttons=نشاندادن دکمههای اشتراکگذاری در شبکههای اجتماعی
|
settings.general.social_buttons=نشاندادن دکمههای اشتراکگذاری در شبکههای اجتماعی
|
||||||
settings.general.scroll_marks=در نمای گسترشیافته، لغزیدن بر روی مطالب بهعنوان نشانهگذاری بهعنوان خواندهشده در نظر گرفتهشوند.
|
settings.general.scroll_marks=در نمای گسترشیافته، لغزیدن بر روی مطالب بهعنوان نشانهگذاری بهعنوان خواندهشده در نظر گرفتهشوند.
|
||||||
settings.appearance=ظاهر
|
settings.appearance=ظاهر
|
||||||
|
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
|
||||||
|
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
|
||||||
settings.theme=پوسته
|
settings.theme=پوسته
|
||||||
settings.submit_your_theme=شیمه پوستهٰ اوسه کونید
|
settings.submit_your_theme=شیمه پوستهٰ اوسه کونید
|
||||||
settings.custom_css=سیاساس شخصیسازیشده
|
settings.custom_css=سیاساس شخصیسازیشده
|
||||||
@@ -83,7 +87,10 @@ details.queued_for_refresh=منتظر برای بروزرسانی
|
|||||||
details.feed_url=نشانی خوراک
|
details.feed_url=نشانی خوراک
|
||||||
details.generate_api_key_first=ابتدا یک کلید API در نمایهٔ خود ایجاد کنید.
|
details.generate_api_key_first=ابتدا یک کلید API در نمایهٔ خود ایجاد کنید.
|
||||||
details.unsubscribe=لغو اشتراک
|
details.unsubscribe=لغو اشتراک
|
||||||
|
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
|
||||||
|
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
|
||||||
details.category_details=جرگه جزئیات
|
details.category_details=جرگه جزئیات
|
||||||
|
details.tag_details=Tag details ####### Needs translation
|
||||||
details.parent_category=پئرˇ جرگه
|
details.parent_category=پئرˇ جرگه
|
||||||
|
|
||||||
profile.user_name=کاربری نام
|
profile.user_name=کاربری نام
|
||||||
@@ -98,6 +105,7 @@ profile.generate_new_api_key=تازه کلید چاگودن API
|
|||||||
profile.generate_new_api_key_info=رمزه عوضأگودن API کلیده چاکونه.
|
profile.generate_new_api_key_info=رمزه عوضأگودن API کلیده چاکونه.
|
||||||
profile.opml_export=برینأدأن OPML
|
profile.opml_export=برینأدأن OPML
|
||||||
profile.delete_account=کاربری حسابه پاکودن
|
profile.delete_account=کاربری حسابه پاکودن
|
||||||
|
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
|
||||||
|
|
||||||
about.rest_api=REST API
|
about.rest_api=REST API
|
||||||
about.keyboard_shortcuts=وئر زئنˇ کلیدان
|
about.keyboard_shortcuts=وئر زئنˇ کلیدان
|
||||||
|
|||||||
@@ -1,149 +1,157 @@
|
|||||||
global.save=Mentés
|
global.save=Mentés
|
||||||
global.cancel=Mégsem
|
global.cancel=Mégsem
|
||||||
global.delete=Törlés
|
global.delete=Törlés
|
||||||
global.required=Szükséges
|
global.required=Szükséges
|
||||||
global.download=Letöltés
|
global.download=Letöltés
|
||||||
global.link=Link
|
global.link=Link
|
||||||
global.bookmark=Könyvjelző
|
global.bookmark=Könyvjelző
|
||||||
global.close=Bezár
|
global.close=Bezár
|
||||||
|
global.tags=Címkék
|
||||||
tree.subscribe=Feliratkozás
|
|
||||||
tree.import=Importálás
|
tree.subscribe=Feliratkozás
|
||||||
tree.new_category=Új kategória
|
tree.import=Importálás
|
||||||
tree.all=Összes
|
tree.new_category=Új kategória
|
||||||
tree.starred=Csillagozott
|
tree.all=Összes
|
||||||
|
tree.starred=Csillagozott
|
||||||
subscribe.feed_url=Hírcsatorna URL
|
|
||||||
subscribe.feed_name=Hírcsatorna neve
|
subscribe.feed_url=Hírcsatorna URL
|
||||||
subscribe.category=Kategória
|
subscribe.feed_name=Hírcsatorna neve
|
||||||
|
subscribe.category=Kategória
|
||||||
import.google_reader_prefix=Engedd meg, hogy importáljuk a hírcsatornáidat a
|
|
||||||
import.google_reader_suffix= fiókjából.
|
import.google_reader_prefix=Engedd meg, hogy importáljuk a hírcsatornáidat a
|
||||||
import.google_download=Alternatívaként, feltöltheti a subscriptions.xml fájlt.
|
import.google_reader_suffix= fiókjából.
|
||||||
import.google_download_link=Letöltheti innen.
|
import.google_download=Alternatívaként, feltöltheti a subscriptions.xml fájlt.
|
||||||
import.xml_file=OPML Fájl
|
import.google_download_link=Letöltheti innen.
|
||||||
|
import.xml_file=OPML Fájl
|
||||||
new_category.name=Név
|
|
||||||
new_category.parent=Szülő
|
new_category.name=Név
|
||||||
|
new_category.parent=Szülő
|
||||||
toolbar.unread=Olvasatlan
|
|
||||||
toolbar.all=Összes
|
toolbar.unread=Olvasatlan
|
||||||
toolbar.previous_entry=Előző elem
|
toolbar.all=Összes
|
||||||
toolbar.next_entry=Következő elem
|
toolbar.previous_entry=Előző elem
|
||||||
toolbar.refresh=Frissítés
|
toolbar.next_entry=Következő elem
|
||||||
toolbar.refresh_all=Force refresh all my feeds ####### Needs translation
|
toolbar.refresh=Frissítés
|
||||||
toolbar.sort_by_asc_desc=Rendezés időrend szerint
|
toolbar.refresh_all=Force refresh all my feeds ####### Needs translation
|
||||||
toolbar.titles_only=Csak cím
|
toolbar.sort_by_asc_desc=Rendezés időrend szerint
|
||||||
toolbar.expanded_view=Részletes nézet
|
toolbar.titles_only=Csak cím
|
||||||
toolbar.mark_all_as_read=Az összes megjelölése olvasottként
|
toolbar.expanded_view=Részletes nézet
|
||||||
toolbar.mark_all_older_day=Régebbiek, mint egy nap
|
toolbar.mark_all_as_read=Az összes megjelölése olvasottként
|
||||||
toolbar.mark_all_older_week=Régebbiek, mint egy hét
|
toolbar.mark_all_older_12_hours=Régebbiek 12 óránál
|
||||||
toolbar.mark_all_older_two_weeks=Régebbiek, mint két hét
|
toolbar.mark_all_older_day=Régebbiek, mint egy nap
|
||||||
toolbar.settings=Beállítások
|
toolbar.mark_all_older_week=Régebbiek, mint egy hét
|
||||||
toolbar.profile=Profil
|
toolbar.mark_all_older_two_weeks=Régebbiek, mint két hét
|
||||||
toolbar.admin=Admin
|
toolbar.settings=Beállítások
|
||||||
toolbar.about=Névjegy
|
toolbar.profile=Profil
|
||||||
toolbar.logout=Kilépés
|
toolbar.admin=Admin
|
||||||
toolbar.donate=Anyagi támogatás
|
toolbar.about=Névjegy
|
||||||
|
toolbar.logout=Kilépés
|
||||||
view.entry_source=from ####### Needs translation
|
toolbar.donate=Anyagi támogatás
|
||||||
view.entry_author=by ####### Needs translation
|
|
||||||
view.error_while_loading_feed=Hiba történt ennek a hírcsatornának a betöltésekor
|
view.entry_source=forrás
|
||||||
view.keep_unread=Megtartása olvasatlanként
|
view.entry_author=szerző
|
||||||
view.no_unread_items=nincsen olvasatlan eleme.
|
view.error_while_loading_feed=Hiba történt ennek a hírcsatornának a betöltésekor
|
||||||
view.mark_up_to_here=Mark as read up to here ####### Needs translation
|
view.keep_unread=Megtartása olvasatlanként
|
||||||
view.search_for=searching for: ####### Needs translation
|
view.no_unread_items=nincsen olvasatlan eleme.
|
||||||
view.no_search_results=No match found for the requested keywords ####### Needs translation
|
view.mark_up_to_here=Megjelölés olvasottnak eddig
|
||||||
|
view.search_for=keresés erre:
|
||||||
feedsearch.hint=Keressen a hírcsatornák között...
|
view.no_search_results=Nem található semmi erre a keresett szóra
|
||||||
feedsearch.help=Használja a nyíl billentyűket a navigáláshoz, az enter-t a kiválasztáshoz.
|
|
||||||
feedsearch.result_prefix=Az ön feliratkozásai:
|
feedsearch.hint=Keressen a hírcsatornák között...
|
||||||
|
feedsearch.help=Használja a nyíl billentyűket a navigáláshoz, az enter-t a kiválasztáshoz.
|
||||||
settings.general=Általános
|
feedsearch.result_prefix=Az ön feliratkozásai:
|
||||||
settings.general.language=Nyelv
|
|
||||||
settings.general.language.contribute=Segítsen a fordításban
|
settings.general=Általános
|
||||||
settings.general.show_unread=Mutassa azokat a hírcsatornákat és kategóriákat amelyekben nincsen olvasatlan bejegyzés
|
settings.general.language=Nyelv
|
||||||
settings.general.social_buttons=Mutassa a közösségi oldalak megosztás gombjait
|
settings.general.language.contribute=Segítsen a fordításban
|
||||||
settings.general.scroll_marks=Kiterjesztett nézetben, görgetéssel olvasottként jelöli meg a bejegyzést
|
settings.general.show_unread=Mutassa azokat a hírcsatornákat és kategóriákat amelyekben nincsen olvasatlan bejegyzés
|
||||||
settings.appearance=Megjelenés
|
settings.general.social_buttons=Mutassa a közösségi oldalak megosztás gombjait
|
||||||
settings.theme=Téma
|
settings.general.scroll_marks=Kiterjesztett nézetben, görgetéssel olvasottként jelöli meg a bejegyzést
|
||||||
settings.submit_your_theme=Küldje el a témáját
|
settings.appearance=Megjelenés
|
||||||
settings.custom_css=Saját CSS
|
settings.scroll_speed=A görgetés sebessége, amikor a cikkek között navigál (miliszekundumban)
|
||||||
|
settings.scroll_speed.help=Írjon be 0-át a letiltáshoz
|
||||||
details.feed_details=Hírcsatorna részletei
|
settings.theme=Téma
|
||||||
details.url=URL
|
settings.submit_your_theme=Küldje el a témáját
|
||||||
details.website=Website ####### Needs translation
|
settings.custom_css=Saját CSS
|
||||||
details.name=Név
|
|
||||||
details.category=Kategória
|
details.feed_details=Hírcsatorna részletei
|
||||||
details.position=Position ####### Needs translation
|
details.url=URL
|
||||||
details.last_refresh=Utolsó frissítés
|
details.website=Weboldal
|
||||||
details.message=Last refresh message ####### Needs translation
|
details.name=Név
|
||||||
details.next_refresh=Következő frissítés
|
details.category=Kategória
|
||||||
details.queued_for_refresh=Frissítésre vár
|
details.position=Pozició
|
||||||
details.feed_url=Hírcsatorna URL
|
details.last_refresh=Utolsó frissítés
|
||||||
details.generate_api_key_first=A profiljában először egy API kulcsot kell generálnia.
|
details.message=Utolsó frissítési üzenet
|
||||||
details.unsubscribe=Leiratkozás
|
details.next_refresh=Következő frissítés
|
||||||
details.category_details=Kategória részletei
|
details.queued_for_refresh=Frissítésre vár
|
||||||
details.parent_category=Szülő kategória
|
details.feed_url=Hírcsatorna URL
|
||||||
|
details.generate_api_key_first=A profiljában először egy API kulcsot kell generálnia.
|
||||||
profile.user_name=Felhasználói név
|
details.unsubscribe=Leiratkozás
|
||||||
profile.email=E-mail
|
details.unsubscribe_confirmation=Biztos, hogy le akar iratkozni errről a csatornáról?
|
||||||
profile.change_password=Jelszó megváltoztatás
|
details.delete_category_confirmation=Biztos, hog törölni akarja ezt a kategóriát?
|
||||||
profile.confirm_password=Jelszó megerősítése
|
details.category_details=Kategória részletei
|
||||||
profile.minimum_6_chars=Legalább 8 karakter
|
details.tag_details=Címke részletei
|
||||||
profile.passwords_do_not_match=A jelszavak nem egyeznek
|
details.parent_category=Szülő kategória
|
||||||
profile.api_key=API kulcs
|
|
||||||
profile.api_key_not_generated=Még nincsen generálva
|
profile.user_name=Felhasználói név
|
||||||
profile.generate_new_api_key=Új API kulcs generálása
|
profile.email=E-mail
|
||||||
profile.generate_new_api_key_info=A jelszó megváltoztatása új API kulcsot generál
|
profile.change_password=Jelszó megváltoztatás
|
||||||
profile.opml_export=OPML exportálása
|
profile.confirm_password=Jelszó megerősítése
|
||||||
profile.delete_account=Fiók törlése
|
profile.minimum_6_chars=Legalább 8 karakter
|
||||||
|
profile.passwords_do_not_match=A jelszavak nem egyeznek
|
||||||
about.rest_api=REST API
|
profile.api_key=API kulcs
|
||||||
about.keyboard_shortcuts=Gyorsbillentyűk
|
profile.api_key_not_generated=Még nincsen generálva
|
||||||
about.version=CommaFeed version ####### Needs translation
|
profile.generate_new_api_key=Új API kulcs generálása
|
||||||
about.line1_prefix=A CommaFeed egy nyílt forrású projekt. A forrás megtalálható a
|
profile.generate_new_api_key_info=A jelszó megváltoztatása új API kulcsot generál
|
||||||
about.line1_suffix=oldalán.
|
profile.opml_export=OPML exportálása
|
||||||
about.line2_prefix=Ha hibába ütközik, kérjük jelentse azt a
|
profile.delete_account=Fiók törlése
|
||||||
about.line2_suffix=projekt oldalán.
|
profile.delete_account_confirmation=Törli a fiókját? Innen már nincs visszatérés!
|
||||||
about.line3=Ha tetszik önnek ez a szolgáltatás, akkor kérjük támogassa a fejlesztőket és, hogy fentarthassák a weboldalt.
|
|
||||||
about.line4=Akik jobban szeretnék az oldalt bitcon-nal támogatni, itt a cím
|
about.rest_api=REST API
|
||||||
about.goodies=Hasznos dolgok
|
about.keyboard_shortcuts=Gyorsbillentyűk
|
||||||
about.goodies.android_app=Android app ####### Needs translation
|
about.version=CommaFeed verzió
|
||||||
about.goodies.subscribe_url=Feliratkozás az URL-re
|
about.line1_prefix=A CommaFeed egy nyílt forrású projekt. A forrás megtalálható a
|
||||||
about.goodies.chrome_extension=Chrome bővítmény
|
about.line1_suffix=oldalán.
|
||||||
about.goodies.firefox_extension=Firefox kiterjesztés
|
about.line2_prefix=Ha hibába ütközik, kérjük jelentse azt a
|
||||||
about.goodies.opera_extension=Opera kiterjesztés
|
about.line2_suffix=projekt oldalán.
|
||||||
about.goodies.subscribe_bookmarklet=Feliratkozás bookmarklet hozzáadása (klikkeléssel)
|
about.line3=Ha tetszik önnek ez a szolgáltatás, akkor kérjük támogassa a fejlesztőket és, hogy fentarthassák a weboldalt.
|
||||||
about.goodies.subscribe_bookmarklet_asc=Oldest first ####### Needs translation
|
about.line4=Akik jobban szeretnék az oldalt bitcon-nal támogatni, itt a cím
|
||||||
about.goodies.subscribe_bookmarklet_desc=Newest first ####### Needs translation
|
about.goodies=Hasznos dolgok
|
||||||
about.goodies.next_unread_bookmarklet=Következő olvasatlan elem bookmarklet (húzza fel a könyvjelzősávba)
|
about.goodies.android_app=Android alkalmazás
|
||||||
about.translation=Fordítás
|
about.goodies.subscribe_url=Feliratkozás az URL-re
|
||||||
about.translation.message=Segítségét kérjük a CommaFeed fordításához.
|
about.goodies.chrome_extension=Chrome bővítmény
|
||||||
about.translation.link=Nézze meg, hogyan tud segíteni ebben.
|
about.goodies.firefox_extension=Firefox kiterjesztés
|
||||||
about.announcements=Bejelentések
|
about.goodies.opera_extension=Opera kiterjesztés
|
||||||
about.rest_api.line1=A CommaFeed a JAX-RS-re és az AngularJS-re épül. Ezért a RESTA API elérhető.
|
about.goodies.subscribe_bookmarklet=Feliratkozás bookmarklet hozzáadása (klikkeléssel)
|
||||||
about.rest_api.link_to_documentation=Link a dokumentációhoz.
|
about.goodies.subscribe_bookmarklet_asc=Régebbiek először
|
||||||
|
about.goodies.subscribe_bookmarklet_desc=Újak először
|
||||||
about.shortcuts.mouse_middleclick=középső egérgomb
|
about.goodies.next_unread_bookmarklet=Következő olvasatlan elem bookmarklet (húzza fel a könyvjelzősávba)
|
||||||
about.shortcuts.open_next_entry=következő hír megnyitása
|
about.translation=Fordítás
|
||||||
about.shortcuts.open_previous_entry=előző hír megnyitása
|
about.translation.message=Segítségét kérjük a CommaFeed fordításához.
|
||||||
about.shortcuts.spacebar=space/shift+space ####### Needs translation
|
about.translation.link=Nézze meg, hogyan tud segíteni ebben.
|
||||||
about.shortcuts.move_page_down_up=moves the page down/up ####### Needs translation
|
about.announcements=Bejelentések
|
||||||
about.shortcuts.focus_next_entry=megnyitás nélkül fókuszál a övetkező elemre
|
about.rest_api.line1=A CommaFeed a JAX-RS-re és az AngularJS-re épül. Ezért a RESTA API elérhető.
|
||||||
about.shortcuts.focus_previous_entry=megnyitás nélkül fókuszál az előző elemre
|
about.rest_api.link_to_documentation=Link a dokumentációhoz.
|
||||||
about.shortcuts.open_next_feed=a következő hírcsatorna vagy kategória megnyitása
|
|
||||||
about.shortcuts.open_previous_feed=az előző hírcsatorna vagy kategória megnyitása
|
about.shortcuts.mouse_middleclick=középső egérgomb
|
||||||
about.shortcuts.open_close_current_entry=a jelenlegi elem megnyitása/bezárása
|
about.shortcuts.open_next_entry=következő hír megnyitása
|
||||||
about.shortcuts.open_current_entry_in_new_window=a jelenlegi elem megnyitása új ablakban
|
about.shortcuts.open_previous_entry=előző hír megnyitása
|
||||||
about.shortcuts.open_current_entry_in_new_window_background=a jelenlegi elem megnyitása a háttérben, új ablakban
|
about.shortcuts.spacebar=szóköz/shift+szóköz
|
||||||
about.shortcuts.star_unstar=hírelem csillagozása
|
about.shortcuts.move_page_down_up=fel/le lépkedhet az oldalon
|
||||||
about.shortcuts.mark_current_entry=elem megjelölése olvasottként
|
about.shortcuts.focus_next_entry=megnyitás nélkül fókuszál a övetkező elemre
|
||||||
about.shortcuts.mark_all_as_read=az összes elem megjelölése olvasottként
|
about.shortcuts.focus_previous_entry=megnyitás nélkül fókuszál az előző elemre
|
||||||
about.shortcuts.open_in_new_tab_mark_as_read=elem megnyitása új fülön és megjelölése olvasottként
|
about.shortcuts.open_next_feed=a következő hírcsatorna vagy kategória megnyitása
|
||||||
about.shortcuts.fullscreen=toggle full screen mode ####### Needs translation
|
about.shortcuts.open_previous_feed=az előző hírcsatorna vagy kategória megnyitása
|
||||||
about.shortcuts.font_size=increase/decrease font size of the current entry ####### Needs translation
|
about.shortcuts.open_close_current_entry=a jelenlegi elem megnyitása/bezárása
|
||||||
about.shortcuts.go_to_all=go to the All view ####### Needs translation
|
about.shortcuts.open_current_entry_in_new_window=a jelenlegi elem megnyitása új ablakban
|
||||||
about.shortcuts.go_to_starred=go to the Starred view ####### Needs translation
|
about.shortcuts.open_current_entry_in_new_window_background=a jelenlegi elem megnyitása a háttérben, új ablakban
|
||||||
about.shortcuts.feed_search=név szerinti keresés a hírcsatornák között
|
about.shortcuts.star_unstar=hírelem csillagozása
|
||||||
|
about.shortcuts.mark_current_entry=elem megjelölése olvasottként
|
||||||
|
about.shortcuts.mark_all_as_read=az összes elem megjelölése olvasottként
|
||||||
|
about.shortcuts.open_in_new_tab_mark_as_read=elem megnyitása új fülön és megjelölése olvasottként
|
||||||
|
about.shortcuts.fullscreen=teljes képernyős mód bekapcsolása
|
||||||
|
about.shortcuts.font_size=a jelenlegi elemnél növeli/csökkenti a betűméretet
|
||||||
|
about.shortcuts.go_to_all=átkapcsol az Összes nézetre
|
||||||
|
about.shortcuts.go_to_starred=átkapcsol a Csillagozott nézetre
|
||||||
|
about.shortcuts.feed_search=név szerinti keresés a hírcsatornák között
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ global.download=Download
|
|||||||
global.link=Link
|
global.link=Link
|
||||||
global.bookmark=Segnalibro
|
global.bookmark=Segnalibro
|
||||||
global.close=Chiudi
|
global.close=Chiudi
|
||||||
|
global.tags=Tags ####### Needs translation
|
||||||
|
|
||||||
tree.subscribe=Iscriviti
|
tree.subscribe=Iscriviti
|
||||||
tree.import=Importa
|
tree.import=Importa
|
||||||
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sort by date asc/desc
|
|||||||
toolbar.titles_only=Solo titoli
|
toolbar.titles_only=Solo titoli
|
||||||
toolbar.expanded_view=Espandi
|
toolbar.expanded_view=Espandi
|
||||||
toolbar.mark_all_as_read=Contrassegna tutto come già letto
|
toolbar.mark_all_as_read=Contrassegna tutto come già letto
|
||||||
|
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
|
||||||
toolbar.mark_all_older_day=Elementi più vecchi di un giorno
|
toolbar.mark_all_older_day=Elementi più vecchi di un giorno
|
||||||
toolbar.mark_all_older_week=Elementi più vecchi di una settimana
|
toolbar.mark_all_older_week=Elementi più vecchi di una settimana
|
||||||
toolbar.mark_all_older_two_weeks=Elementi più vecchi di due settimane
|
toolbar.mark_all_older_two_weeks=Elementi più vecchi di due settimane
|
||||||
@@ -66,6 +68,8 @@ settings.general.show_unread=Show feeds and categories with no unread entries
|
|||||||
settings.general.social_buttons=Visualizza i social button
|
settings.general.social_buttons=Visualizza i social button
|
||||||
settings.general.scroll_marks=Marca come letto quando scorri
|
settings.general.scroll_marks=Marca come letto quando scorri
|
||||||
settings.appearance=Appearance ####### Needs translation
|
settings.appearance=Appearance ####### Needs translation
|
||||||
|
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
|
||||||
|
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
|
||||||
settings.theme=Tema
|
settings.theme=Tema
|
||||||
settings.submit_your_theme=Sottoponi il tuo tema
|
settings.submit_your_theme=Sottoponi il tuo tema
|
||||||
settings.custom_css=Css modificato
|
settings.custom_css=Css modificato
|
||||||
@@ -83,7 +87,10 @@ details.queued_for_refresh=In attesa per l'aggiornamento
|
|||||||
details.feed_url=Feed URL
|
details.feed_url=Feed URL
|
||||||
details.generate_api_key_first=Generate an API key in your profile first.
|
details.generate_api_key_first=Generate an API key in your profile first.
|
||||||
details.unsubscribe=Annulla l"'"iscrizione
|
details.unsubscribe=Annulla l"'"iscrizione
|
||||||
|
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
|
||||||
|
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
|
||||||
details.category_details=Dettagli categoria
|
details.category_details=Dettagli categoria
|
||||||
|
details.tag_details=Tag details ####### Needs translation
|
||||||
details.parent_category=Parent category
|
details.parent_category=Parent category
|
||||||
|
|
||||||
profile.user_name=User name
|
profile.user_name=User name
|
||||||
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Genera una nuova chiave API
|
|||||||
profile.generate_new_api_key_info=Cambiando la password sarà generata una nuova chiave API
|
profile.generate_new_api_key_info=Cambiando la password sarà generata una nuova chiave API
|
||||||
profile.opml_export=Esporta OPML
|
profile.opml_export=Esporta OPML
|
||||||
profile.delete_account=Elimina account
|
profile.delete_account=Elimina account
|
||||||
|
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
|
||||||
|
|
||||||
about.rest_api=REST API
|
about.rest_api=REST API
|
||||||
about.keyboard_shortcuts=Scorciatoie da tastiera
|
about.keyboard_shortcuts=Scorciatoie da tastiera
|
||||||
|
|||||||
157
src/main/resources/i18n/ja.properties
Normal file
157
src/main/resources/i18n/ja.properties
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
global.save=保存
|
||||||
|
global.cancel=取り消し
|
||||||
|
global.delete=削除
|
||||||
|
global.required=Required
|
||||||
|
global.download=ダウンロード
|
||||||
|
global.link=リンク
|
||||||
|
global.bookmark=ブックマーク
|
||||||
|
global.close=閉じる
|
||||||
|
global.tags=タグ
|
||||||
|
|
||||||
|
tree.subscribe=購読
|
||||||
|
tree.import=インポート
|
||||||
|
tree.new_category=新しいカテゴリー
|
||||||
|
tree.all=全て
|
||||||
|
tree.starred=スター付
|
||||||
|
|
||||||
|
subscribe.feed_url=フィードURL
|
||||||
|
subscribe.feed_name=フィード名
|
||||||
|
subscribe.category=カテゴリー
|
||||||
|
|
||||||
|
import.google_reader_prefix=Googleアカウントからフィードを
|
||||||
|
import.google_reader_suffix=インポートします。
|
||||||
|
import.google_download=または、お持ちのsubscriptions.xmlファイルをアップロードします。
|
||||||
|
import.google_download_link=このリンクからダウンロードして下さい。
|
||||||
|
import.xml_file=OPMLファイル
|
||||||
|
|
||||||
|
new_category.name=名前
|
||||||
|
new_category.parent=親カテゴリー
|
||||||
|
|
||||||
|
toolbar.unread=未読
|
||||||
|
toolbar.all=全て
|
||||||
|
toolbar.previous_entry=前のエントリー
|
||||||
|
toolbar.next_entry=次のエントリー
|
||||||
|
toolbar.refresh=更新
|
||||||
|
toolbar.refresh_all=全てのフィードを更新
|
||||||
|
toolbar.sort_by_asc_desc=昇順/降順にソート
|
||||||
|
toolbar.titles_only=タイトルのみ
|
||||||
|
toolbar.expanded_view=拡張ビュー
|
||||||
|
toolbar.mark_all_as_read=全て既読にする
|
||||||
|
toolbar.mark_all_older_12_hours=12時間以上前のアイテム
|
||||||
|
toolbar.mark_all_older_day=前日より前のアイテム
|
||||||
|
toolbar.mark_all_older_week=1週間以上前のアイテム
|
||||||
|
toolbar.mark_all_older_two_weeks=2週間以上前のアイテム
|
||||||
|
toolbar.settings=設定
|
||||||
|
toolbar.profile=Profile
|
||||||
|
toolbar.admin=管理者
|
||||||
|
toolbar.about=About
|
||||||
|
toolbar.logout=ログアウト
|
||||||
|
toolbar.donate=寄付
|
||||||
|
|
||||||
|
view.entry_source= より
|
||||||
|
view.entry_author= 著者
|
||||||
|
view.error_while_loading_feed=フィード読み込み中にエラーが発生しました。
|
||||||
|
view.keep_unread=未読として保持
|
||||||
|
view.no_unread_items=未読アイテムはありません。
|
||||||
|
view.mark_up_to_here=ここまで既読
|
||||||
|
view.search_for=検索:
|
||||||
|
view.no_search_results=検索結果はありません。
|
||||||
|
|
||||||
|
feedsearch.hint=購読フィードを入力...
|
||||||
|
feedsearch.help=Enterキーで選択、矢印キーで移動します。
|
||||||
|
feedsearch.result_prefix=見つかった購読フィード:
|
||||||
|
|
||||||
|
settings.general=一般
|
||||||
|
settings.general.language=言語
|
||||||
|
settings.general.language.contribute=翻訳に貢献する
|
||||||
|
settings.general.show_unread=未読エントリーのないフィードとカテゴリーを表示
|
||||||
|
settings.general.social_buttons=共有ボタンを表示
|
||||||
|
settings.general.scroll_marks=拡張ビューではエントリーのスクロールで既読にする
|
||||||
|
settings.appearance=外観
|
||||||
|
settings.scroll_speed=エントリー間のスクロールスピード(ミリ秒)
|
||||||
|
settings.scroll_speed.help=0に設定すると無効になります
|
||||||
|
settings.theme=テーマ
|
||||||
|
settings.submit_your_theme=テーマを登録する
|
||||||
|
settings.custom_css=カスタムCSS
|
||||||
|
|
||||||
|
details.feed_details=フィードの詳細
|
||||||
|
details.url=URL
|
||||||
|
details.website=Webサイト
|
||||||
|
details.name=名前
|
||||||
|
details.category=カテゴリー
|
||||||
|
details.position=位置
|
||||||
|
details.last_refresh=最終更新
|
||||||
|
details.message=最終更新メッセージ
|
||||||
|
details.next_refresh=次回更新
|
||||||
|
details.queued_for_refresh=更新キュー
|
||||||
|
details.feed_url=フィードURL
|
||||||
|
details.generate_api_key_first=最初にあなたのAPIキーを生成して下さい。
|
||||||
|
details.unsubscribe=購読解除
|
||||||
|
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
|
||||||
|
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
|
||||||
|
details.category_details=カテゴリー詳細
|
||||||
|
details.tag_details=タグ詳細
|
||||||
|
details.parent_category=親カテゴリー
|
||||||
|
|
||||||
|
profile.user_name=ユーザ名
|
||||||
|
profile.email=E-mail
|
||||||
|
profile.change_password=パスワードの変更
|
||||||
|
profile.confirm_password=変更パスワードの確認
|
||||||
|
profile.minimum_6_chars=6文字以上
|
||||||
|
profile.passwords_do_not_match=パスワードが一致しません
|
||||||
|
profile.api_key=APIキー
|
||||||
|
profile.api_key_not_generated=APIキーが生成されていません
|
||||||
|
profile.generate_new_api_key=新しいAPIキーを生成
|
||||||
|
profile.generate_new_api_key_info=パスワードの変更は新しいAPIキーが生成されます
|
||||||
|
profile.opml_export=OPMLエクスポート
|
||||||
|
profile.delete_account=アカウント削除
|
||||||
|
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
|
||||||
|
|
||||||
|
about.rest_api=REST API
|
||||||
|
about.keyboard_shortcuts=キーボードショートカット
|
||||||
|
about.version=CommaFeedバージョン
|
||||||
|
about.line1_prefix=CommaFeedはオープンソースプロジェクトです。ソースは
|
||||||
|
about.line1_suffix=にホスティングされています。
|
||||||
|
about.line2_prefix=もし問題を登録したい場合、
|
||||||
|
about.line2_suffix=プロジェクトのissuesページに報告して下さい。
|
||||||
|
about.line3=このプロジェクトを気に入った場合、開発者やWebサイトの運営コストをサポートするための寄付を検討して下さいね。
|
||||||
|
about.line4=Bitcoinなら寄付できる方、アドレスはこちらです。
|
||||||
|
about.goodies=Goodies
|
||||||
|
about.goodies.android_app=Androidアプリ
|
||||||
|
about.goodies.subscribe_url=購読URL
|
||||||
|
about.goodies.chrome_extension=Chrome拡張
|
||||||
|
about.goodies.firefox_extension=Firefox拡張
|
||||||
|
about.goodies.opera_extension=Opera拡張
|
||||||
|
about.goodies.subscribe_bookmarklet=購読ブックマークレットを追加(クリック)
|
||||||
|
about.goodies.subscribe_bookmarklet_asc=古い順
|
||||||
|
about.goodies.subscribe_bookmarklet_desc=新しい順
|
||||||
|
about.goodies.next_unread_bookmarklet=次の未読アイテムブックマークレット(ブックマークバーへドラッグ)
|
||||||
|
about.translation=翻訳
|
||||||
|
about.translation.message=CommaFeedの翻訳に助けが必要です!
|
||||||
|
about.translation.link=どうやって翻訳に貢献できるか見て下さい。
|
||||||
|
about.announcements=Announcements
|
||||||
|
about.rest_api.line1=CommaFeedはJAX-RSとAngularJSを使用しているので、REST APIも利用可能です。
|
||||||
|
about.rest_api.link_to_documentation=ドキュメントへのリンク
|
||||||
|
|
||||||
|
about.shortcuts.mouse_middleclick=中クリック
|
||||||
|
about.shortcuts.open_next_entry=次のエントリーを開く
|
||||||
|
about.shortcuts.open_previous_entry=前のエントリーを開く
|
||||||
|
about.shortcuts.spacebar=space/shift+space
|
||||||
|
about.shortcuts.move_page_down_up=ページ移動
|
||||||
|
about.shortcuts.focus_next_entry=次のエントリーを開かずにフォーカス移動
|
||||||
|
about.shortcuts.focus_previous_entry=前のエントリーを開かずにフォーカス移動
|
||||||
|
about.shortcuts.open_next_feed=次のフィード/カテゴリーを開く
|
||||||
|
about.shortcuts.open_previous_feed=前のフィード/カテゴリーを開く
|
||||||
|
about.shortcuts.open_close_current_entry=現在のエントリーを開く/閉じる
|
||||||
|
about.shortcuts.open_current_entry_in_new_window=現在のエントリーを新しいウィンドウで開く
|
||||||
|
about.shortcuts.open_current_entry_in_new_window_background=現在のエントリーを新しいバックグラウンドウィンドウで開く
|
||||||
|
about.shortcuts.star_unstar=現在のエントリーにスターを付ける/解除する
|
||||||
|
about.shortcuts.mark_current_entry=現在のエントリーを既読/未読にする
|
||||||
|
about.shortcuts.mark_all_as_read=全エントリーを既読にする
|
||||||
|
about.shortcuts.open_in_new_tab_mark_as_read=エントリーを既読にして新しいタブで開く
|
||||||
|
about.shortcuts.fullscreen=フルスクリーントグル
|
||||||
|
about.shortcuts.font_size=現在のエントリーのフォントサイズを大きく/小さくする
|
||||||
|
about.shortcuts.go_to_all=All viewに変更する
|
||||||
|
about.shortcuts.go_to_starred=スター付きviewに変更する
|
||||||
|
about.shortcuts.feed_search=購読画面(subscription nameの入力)に移動する
|
||||||
|
|
||||||
@@ -6,6 +6,7 @@ global.download=Download ####### Needs translation
|
|||||||
global.link=Link ####### Needs translation
|
global.link=Link ####### Needs translation
|
||||||
global.bookmark=Bookmark ####### Needs translation
|
global.bookmark=Bookmark ####### Needs translation
|
||||||
global.close=Close ####### Needs translation
|
global.close=Close ####### Needs translation
|
||||||
|
global.tags=Tags ####### Needs translation
|
||||||
|
|
||||||
tree.subscribe=구독
|
tree.subscribe=구독
|
||||||
tree.import=임포트
|
tree.import=임포트
|
||||||
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Sort by date asc/desc ####### Needs translation
|
|||||||
toolbar.titles_only=Titles only ####### Needs translation
|
toolbar.titles_only=Titles only ####### Needs translation
|
||||||
toolbar.expanded_view=Expanded view ####### Needs translation
|
toolbar.expanded_view=Expanded view ####### Needs translation
|
||||||
toolbar.mark_all_as_read=읽음표시
|
toolbar.mark_all_as_read=읽음표시
|
||||||
|
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
|
||||||
toolbar.mark_all_older_day=Items older than a day ####### Needs translation
|
toolbar.mark_all_older_day=Items older than a day ####### Needs translation
|
||||||
toolbar.mark_all_older_week=Items older than a week ####### Needs translation
|
toolbar.mark_all_older_week=Items older than a week ####### Needs translation
|
||||||
toolbar.mark_all_older_two_weeks=Items older than two weeks ####### Needs translation
|
toolbar.mark_all_older_two_weeks=Items older than two weeks ####### Needs translation
|
||||||
@@ -66,6 +68,8 @@ settings.general.show_unread=안읽은 항목들이 있는 피드와 카테고
|
|||||||
settings.general.social_buttons=소셜미디아 버튼들 보여주기
|
settings.general.social_buttons=소셜미디아 버튼들 보여주기
|
||||||
settings.general.scroll_marks=Expanded View에서 스크롤하면 항목들을 읽음으로 저장하기
|
settings.general.scroll_marks=Expanded View에서 스크롤하면 항목들을 읽음으로 저장하기
|
||||||
settings.appearance=Appearance ####### Needs translation
|
settings.appearance=Appearance ####### Needs translation
|
||||||
|
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
|
||||||
|
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
|
||||||
settings.theme=Theme ####### Needs translation
|
settings.theme=Theme ####### Needs translation
|
||||||
settings.submit_your_theme=Submit your theme ####### Needs translation
|
settings.submit_your_theme=Submit your theme ####### Needs translation
|
||||||
settings.custom_css=커스톰 CSS
|
settings.custom_css=커스톰 CSS
|
||||||
@@ -83,7 +87,10 @@ details.queued_for_refresh=Queued for refresh ####### Needs translation
|
|||||||
details.feed_url=피드 유알엘
|
details.feed_url=피드 유알엘
|
||||||
details.generate_api_key_first=당신의 프로필을 위해 API Key를 먼저 생성하세요.
|
details.generate_api_key_first=당신의 프로필을 위해 API Key를 먼저 생성하세요.
|
||||||
details.unsubscribe=주소 삭제
|
details.unsubscribe=주소 삭제
|
||||||
|
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
|
||||||
|
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
|
||||||
details.category_details=카테고리 세부
|
details.category_details=카테고리 세부
|
||||||
|
details.tag_details=Tag details ####### Needs translation
|
||||||
details.parent_category=부모 카테고리
|
details.parent_category=부모 카테고리
|
||||||
|
|
||||||
profile.user_name=사용자 이름
|
profile.user_name=사용자 이름
|
||||||
@@ -98,6 +105,7 @@ profile.generate_new_api_key=API Key 생성하기
|
|||||||
profile.generate_new_api_key_info=비밀번호를 변경하면 새로운 API Key가 생성됩니다.
|
profile.generate_new_api_key_info=비밀번호를 변경하면 새로운 API Key가 생성됩니다.
|
||||||
profile.opml_export=OPML export ####### Needs translation
|
profile.opml_export=OPML export ####### Needs translation
|
||||||
profile.delete_account=프로필삭제
|
profile.delete_account=프로필삭제
|
||||||
|
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
|
||||||
|
|
||||||
about.rest_api=REST API
|
about.rest_api=REST API
|
||||||
about.keyboard_shortcuts=단축기
|
about.keyboard_shortcuts=단축기
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
ar=العربية
|
ar=العربية
|
||||||
|
ca=Català
|
||||||
en=English
|
en=English
|
||||||
es=Español
|
es=Español
|
||||||
de=Deutsch
|
de=Deutsch
|
||||||
@@ -7,6 +8,7 @@ fr=Français
|
|||||||
gl=Galician
|
gl=Galician
|
||||||
glk=گیلکی
|
glk=گیلکی
|
||||||
hu=Magyar
|
hu=Magyar
|
||||||
|
ja=日本語
|
||||||
ko=한국어
|
ko=한국어
|
||||||
nl=Nederlands
|
nl=Nederlands
|
||||||
nb=Norsk (bokmål)
|
nb=Norsk (bokmål)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ global.download=Muat turun
|
|||||||
global.link=Pautan
|
global.link=Pautan
|
||||||
global.bookmark=Bookmark
|
global.bookmark=Bookmark
|
||||||
global.close=Tutup
|
global.close=Tutup
|
||||||
|
global.tags=Tags ####### Needs translation
|
||||||
|
|
||||||
tree.subscribe=Langgan
|
tree.subscribe=Langgan
|
||||||
tree.import=Import
|
tree.import=Import
|
||||||
@@ -36,6 +37,7 @@ toolbar.sort_by_asc_desc=Aturkan mengikut tarikh (baru/lama)
|
|||||||
toolbar.titles_only=Tajuk sahaja
|
toolbar.titles_only=Tajuk sahaja
|
||||||
toolbar.expanded_view=Wide view
|
toolbar.expanded_view=Wide view
|
||||||
toolbar.mark_all_as_read=Tanda kesemuanya telah dibaca
|
toolbar.mark_all_as_read=Tanda kesemuanya telah dibaca
|
||||||
|
toolbar.mark_all_older_12_hours=Items older than 12 hours ####### Needs translation
|
||||||
toolbar.mark_all_older_day=Lebih lama daripada sehari
|
toolbar.mark_all_older_day=Lebih lama daripada sehari
|
||||||
toolbar.mark_all_older_week=Lebih lama daripada seminggu
|
toolbar.mark_all_older_week=Lebih lama daripada seminggu
|
||||||
toolbar.mark_all_older_two_weeks=Lebih lama daripada dua minggu
|
toolbar.mark_all_older_two_weeks=Lebih lama daripada dua minggu
|
||||||
@@ -66,6 +68,8 @@ settings.general.show_unread=Tunjuk semua feed dan kategori yang telah dibaca
|
|||||||
settings.general.social_buttons=Tunjuk social sharing
|
settings.general.social_buttons=Tunjuk social sharing
|
||||||
settings.general.scroll_marks=Dalam wide view, tanda item dibaca ketika scrolling
|
settings.general.scroll_marks=Dalam wide view, tanda item dibaca ketika scrolling
|
||||||
settings.appearance=Rupa
|
settings.appearance=Rupa
|
||||||
|
settings.scroll_speed=Scrolling speed when navigating between entries (in milliseconds) ####### Needs translation
|
||||||
|
settings.scroll_speed.help=set to 0 to disable ####### Needs translation
|
||||||
settings.theme=Tema
|
settings.theme=Tema
|
||||||
settings.submit_your_theme=Muat naik tema anda
|
settings.submit_your_theme=Muat naik tema anda
|
||||||
settings.custom_css=Custom CSS
|
settings.custom_css=Custom CSS
|
||||||
@@ -83,7 +87,10 @@ details.queued_for_refresh=Diaturkan untuk refresh
|
|||||||
details.feed_url=URL Feed
|
details.feed_url=URL Feed
|
||||||
details.generate_api_key_first=Janakan API key dalam profil anda dahulu.
|
details.generate_api_key_first=Janakan API key dalam profil anda dahulu.
|
||||||
details.unsubscribe=Hentikan langganan
|
details.unsubscribe=Hentikan langganan
|
||||||
|
details.unsubscribe_confirmation=Are you sure you want to unsubscribe from this feed? ####### Needs translation
|
||||||
|
details.delete_category_confirmation=Are you sure you want to delete this category? ####### Needs translation
|
||||||
details.category_details=Butir-butir kategori
|
details.category_details=Butir-butir kategori
|
||||||
|
details.tag_details=Tag details ####### Needs translation
|
||||||
details.parent_category=Kategori induk
|
details.parent_category=Kategori induk
|
||||||
|
|
||||||
profile.user_name=User name
|
profile.user_name=User name
|
||||||
@@ -98,6 +105,7 @@ profile.generate_new_api_key=Jana API key baru
|
|||||||
profile.generate_new_api_key_info=Pertukaran kata laluan akan menjanakan API key yang baru
|
profile.generate_new_api_key_info=Pertukaran kata laluan akan menjanakan API key yang baru
|
||||||
profile.opml_export=Export OPML
|
profile.opml_export=Export OPML
|
||||||
profile.delete_account=Padam akaun
|
profile.delete_account=Padam akaun
|
||||||
|
profile.delete_account_confirmation=Delete your acount? There's no turning back! ####### Needs translation
|
||||||
|
|
||||||
about.rest_api=REST API
|
about.rest_api=REST API
|
||||||
about.keyboard_shortcuts=Pintasan papan kekunci
|
about.keyboard_shortcuts=Pintasan papan kekunci
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user