1.1 --- a/.hgtags Wed Jul 01 10:48:22 2009 +0200
1.2 +++ b/.hgtags Wed Jul 22 14:04:05 2009 +0200
1.3 @@ -1,2 +1,3 @@
1.4 42b394eda04ba06126b04e66606ff9ce769652fc oneThreadPerSocket
1.5 19130f88c6b80cbcda5626c0a49fb35b28a8e3cb sonews-0.5.0
1.6 +88025be745057c45f7b4d63e698e1c9515e3c168 sonews/1.0.0
2.1 --- a/DEBIAN-web/README.Debian Wed Jul 01 10:48:22 2009 +0200
2.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2.3 @@ -1,6 +0,0 @@
2.4 -cync for Debian
2.5 ----------------
2.6 -
2.7 -cync is still a very early alpha version, so be careful using it. You have been warned!
2.8 -
2.9 - -- Jens Mühlenhoff <jens@xerxys.org> Mon, 11 Aug 2008 17:05:23 +0200
3.1 --- a/DEBIAN-web/compat Wed Jul 01 10:48:22 2009 +0200
3.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
3.3 @@ -1,1 +0,0 @@
3.4 -6
4.1 --- a/DEBIAN-web/control Wed Jul 01 10:48:22 2009 +0200
4.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
4.3 @@ -1,15 +0,0 @@
4.4 -Source: sonews
4.5 -Section: web
4.6 -Priority: optional
4.7 -Maintainer: Christian Lins <cli@openoffice.org>
4.8 -Homepage: http://www.sonews.org/
4.9 -Package: sonews-web
4.10 -Version: 0.5.0-beta1
4.11 -Architecture: all
4.12 -Depends: kitten, libjchart2d-java, sonews
4.13 -Description: Webinterface for sonews
4.14 - sonews is a modern Usenet server providing newsgroups via NNTP.
4.15 - The lightweight servlet server kitten is used to provide an optional
4.16 - configuration web interface.
4.17 - This metapackage depends on all required prerequisites to run the
4.18 - servlet based webinterface of sonews.
5.1 --- a/DEBIAN/control Wed Jul 01 10:48:22 2009 +0200
5.2 +++ b/DEBIAN/control Wed Jul 22 14:04:05 2009 +0200
5.3 @@ -4,12 +4,11 @@
5.4 Maintainer: Christian Lins <cli@openoffice.org>
5.5 Homepage: http://www.sonews.org/
5.6 Package: sonews
5.7 -Version: 0.6.0beta
5.8 +Version: 1.0.0
5.9 Architecture: all
5.10 Depends: openjdk-6-jre-headless | openjdk-6-jre | sun-java6-jre | cacao | jamvm, glassfish-mail, libmysql-java
5.11 -Suggests: kitten, libpg-java, mysql-server, libjchart2d-java
5.12 +Suggests: sonews-web, libpg-java, mysql-server, libjchart2d-java
5.13 Description: Usenet news server
5.14 sonews is a modern Usenet server providing newsgroups via NNTP.
5.15 A relational database backend is used to store the news data.
5.16 - The lightweight servlet server kitten is used to provide an optional
5.17 - configuration web interface.
5.18 + The sonews-web providers a configuration web interface.
6.1 --- a/bin/sonews.sh Wed Jul 01 10:48:22 2009 +0200
6.2 +++ b/bin/sonews.sh Wed Jul 22 14:04:05 2009 +0200
6.3 @@ -1,6 +1,7 @@
6.4 #!/bin/bash
6.5 SCRIPTROOT=$(pwd)
6.6 CLASSPATH=$SCRIPTROOT/lib/sonews.jar:\
6.7 +$SCRIPTROOT/lib/sonews-helpers.jar:\
6.8 $SCRIPTROOT/lib/mysql-connector-java.jar:\
6.9 $SCRIPTROOT/lib/glassfish-mail.jar:\
6.10 $SCRIPTROOT/lib/postgresql.jar
7.1 --- a/doc/sonews.xml Wed Jul 01 10:48:22 2009 +0200
7.2 +++ b/doc/sonews.xml Wed Jul 22 14:04:05 2009 +0200
7.3 @@ -57,11 +57,15 @@
7.4 <sect1 label="1.2">
7.5 <title>Roadmap</title>
7.6 <sect2 label="1.2.1">
7.7 - <title>sonews/0.6</title>
7.8 - <para>Planned to implement the XPAT command for searching, correctly
7.9 + <title>sonews/1.0</title>
7.10 + <para>
7.11 + Various minor fixes and code cleanup (Storage and Command interface for
7.12 + the upcoming Plugin API).
7.13 + </para>
7.14 + <para>XPAT command for searching, correctly
7.15 hashed Message-Ids and a news purging command.
7.16 See <ulink url="http://bugs.xerxys.info/">Bugtracker</ulink> for
7.17 - issues with target sonews/0.6.x.</para>
7.18 + issues with target sonews/1.0.x.</para>
7.19 </sect2>
7.20 </sect1>
7.21 </chapter>
7.22 @@ -76,17 +80,14 @@
7.23 <ulink url="http://www.debian.org/doc/manuals/apt-howto/">APT</ulink>
7.24 easily.
7.25 Add the following line to /etc/apt/sources.list:</para>
7.26 - <screen>deb http://packages.xerxys.info/debian/ unstable main
7.27 - </screen>
7.28 + <screen>deb http://packages.xerxys.info/debian/ unstable main</screen>
7.29 <para>And add the GPG-Key for package authentification, see
7.30 <ulink url="http://packages.xerxys.info/debian/">Xerxys Debian Repository</ulink>
7.31 for more details.</para>
7.32 <para>Then force an update of your local package list:</para>
7.33 - <screen># apt-get update
7.34 -</screen>
7.35 + <screen># apt-get update</screen>
7.36 <para>To install sonews and all prerequisites issue the following command:</para>
7.37 - <screen># apt-get install sonews
7.38 - </screen>
7.39 + <screen># apt-get install sonews</screen>
7.40 <para>This method should work for all recent Debian-based distributions
7.41 (<ulink url="http://www.debian.org/">Debian</ulink>, <ulink url="http://www.ubuntu.com/">Ubuntu</ulink>, etc.).</para>
7.42 </sect2>
7.43 @@ -124,11 +125,9 @@
7.44 <para>You will find the SQL Schema definitions in the helpers subdirectory of
7.45 the source and binary distributions. You can create the tables manually using
7.46 this templates or you can use the setup helper:</para>
7.47 - <screen>user@debian$ sonews setup
7.48 -</screen>
7.49 + <screen>user@debian$ sonews setup</screen>
7.50 <para>or on other *nix systems:</para>
7.51 - <screen>user@nix$ java -jar sonews.jar org.sonews.util.DatabaseSetup
7.52 -</screen>
7.53 + <screen>user@nix$ java -jar sonews.jar org.sonews.util.DatabaseSetup</screen>
7.54 <para>The tool will ask for some information about your database environment,
7.55 connect to the database, create the tables and creates a default bootstrap
7.56 config file called sonews.conf.</para>
7.57 @@ -157,7 +156,7 @@
7.58 <listitem>
7.59 <para>
7.60 If set to true every(!) data going through sonews' socket
7.61 - is written to sonews.log. After a night the logfile can be
7.62 + is written to sonews.log. After a high traffic night the logfile can be
7.63 several gigabytes large, so be careful with this setting.
7.64 </para>
7.65 </listitem>
7.66 @@ -165,20 +164,40 @@
7.67 <varlistentry>
7.68 <term>‘<literal>sonews.hostname</literal>’</term>
7.69 <listitem>
7.70 - <para>Canonical name of the server instance. This variable is part of the server's
7.71 -hello message to the client and used to generate Message-Ids.</para>
7.72 + <para>
7.73 + Canonical name of the server instance. This variable is part of
7.74 + the server's hello message to the client and used to generate
7.75 + Message-Ids.
7.76 + It is highly recommended to set sonews.hostname to the full
7.77 + qualified domain name (FQDN) of the host machine.
7.78 + </para>
7.79 </listitem>
7.80 </varlistentry>
7.81 <varlistentry>
7.82 <term>‘<literal>sonews.timeout</literal>’</term>
7.83 <listitem>
7.84 - <para>Socket timeout for client connections in seconds.</para>
7.85 + <para>
7.86 + Socket timeout for client connections in seconds. Default as
7.87 + recommended in RFC3977 is 180 seconds.
7.88 + </para>
7.89 </listitem>
7.90 </varlistentry>
7.91 <varlistentry>
7.92 <term>‘<literal>sonews.port</literal>’</term>
7.93 <listitem>
7.94 - <para>Listening port of sonews daemon.</para>
7.95 + <para>
7.96 + Listening port of sonews daemon. This value can be overridden
7.97 + with the -p command line argument.
7.98 + </para>
7.99 + </listitem>
7.100 + </varlistentry>
7.101 + <varlistentry>
7.102 + <term>‘<literal>sonews.xdaemon.host</literal>’</term>
7.103 + <listitem>
7.104 + <para>
7.105 + Hostname or IP address of the client machine that is allowed to
7.106 + use the XDAEMON command. Default: localhost
7.107 + </para>
7.108 </listitem>
7.109 </varlistentry>
7.110 </variablelist>
7.111 @@ -196,20 +215,37 @@
7.112 -h|-help This output
7.113 -mlgw Enables the Mailinglist Gateway poller
7.114 -p portnumber Port on which sonews is listening for incoming connections.
7.115 - Overrides port settings in config file and database.
7.116 -
7.117 -</screen>
7.118 + Overrides port settings in config file and database.</screen>
7.119 </sect1>
7.120
7.121 <sect1 label="3.3">
7.122 <title>Webinterface</title>
7.123 <para>The package sonews-web provides an optional webinterface that can be used to
7.124 review statistical information and configuration values of sonews.</para>
7.125 - <screen>sonews-web start|stop
7.126 -</screen>
7.127 + <screen>sonews-web start|stop</screen>
7.128 <para>The webinterface uses the the lightweight Servlet Container Kitten and is
7.129 per default listening on HTTP-Port 8080 (go to http://localhost:8080/sonews).</para>
7.130 </sect1>
7.131 +
7.132 + <sect1 label="3.4">
7.133 + <title>Newsgroup configuration</title>
7.134 + <para>
7.135 + Currently some manual work is necessary to create a newsgroup hosted
7.136 + by a sonews instance.
7.137 + </para>
7.138 + <para>
7.139 + One possibility is to talk via Telnet to the sonews instance and
7.140 + use the non-standard command XDAEMON.
7.141 + <screen>telnet localhost 119</screen>
7.142 + <screen>XDAEMON GROUPADD local.test 0</screen>
7.143 + Please note that the XDAEMON command has restricted access and is only
7.144 + available via local connections (default, can be changed with config
7.145 + value sonews.xdaemon.host).
7.146 + </para>
7.147 + <para>
7.148 + You can also use the web interface to create newsgroups.
7.149 + </para>
7.150 + </sect1>
7.151 </chapter>
7.152
7.153 <chapter label="4">
7.154 @@ -217,8 +253,7 @@
7.155 <para>You're welcome to create patches with bugfixes or additional features. The
7.156 Mercurial DSCM makes this step an easy task.</para>
7.157 <para>Just clone the public <ulink url="http://www.selenic.com/mercurial/">Mercurial</ulink> repository:</para>
7.158 - <screen>hg clone http://code.xerxys.info:8000/hg/sonews/trunk sonews-trunk
7.159 -</screen>
7.160 + <screen>hg clone http://code.xerxys.info:8000/hg/sonews/ sonews-trunk</screen>
7.161 <para>Then make your changes, create a bundle of changesets and send this to me via email.
7.162 Or ask for push access to the public repository.</para>
7.163 <para>
8.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
8.2 +++ b/helpers/commands.list Wed Jul 22 14:04:05 2009 +0200
8.3 @@ -0,0 +1,15 @@
8.4 +org.sonews.daemon.command.ArticleCommand
8.5 +org.sonews.daemon.command.CapabilitiesCommand
8.6 +org.sonews.daemon.command.GroupCommand
8.7 +org.sonews.daemon.command.HelpCommand
8.8 +org.sonews.daemon.command.ListCommand
8.9 +org.sonews.daemon.command.ListGroupCommand
8.10 +org.sonews.daemon.command.ModeReaderCommand
8.11 +org.sonews.daemon.command.NewGroupsCommand
8.12 +org.sonews.daemon.command.NextPrevCommand
8.13 +org.sonews.daemon.command.OverCommand
8.14 +org.sonews.daemon.command.PostCommand
8.15 +org.sonews.daemon.command.QuitCommand
8.16 +org.sonews.daemon.command.StatCommand
8.17 +org.sonews.daemon.command.XDaemonCommand
8.18 +org.sonews.daemon.command.XPatCommand
8.19 \ No newline at end of file
9.1 --- a/helpers/database_postgresql8_tmpl.sql Wed Jul 01 10:48:22 2009 +0200
9.2 +++ b/helpers/database_postgresql8_tmpl.sql Wed Jul 22 14:04:05 2009 +0200
9.3 @@ -69,10 +69,11 @@
9.4 group_id INTEGER REFERENCES groups(group_id) ON DELETE CASCADE,
9.5 listaddress VARCHAR(255),
9.6
9.7 - PRIMARY KEY(group_id, listaddress),
9.8 - UNIQUE(listaddress)
9.9 + PRIMARY KEY(group_id, listaddress)
9.10 );
9.11
9.12 +CREATE INDEX listaddress_key ON groups2list USING btree(listaddress);
9.13 +
9.14 /*
9.15 Configuration table, containing key/value pairs
9.16
10.1 --- a/helpers/sonews Wed Jul 01 10:48:22 2009 +0200
10.2 +++ b/helpers/sonews Wed Jul 22 14:04:05 2009 +0200
10.3 @@ -1,6 +1,7 @@
10.4 #!/bin/bash
10.5
10.6 CLASSPATH=/usr/share/java/sonews.jar:\
10.7 +/usr/share/java/sonews-helpers.jar:\
10.8 /usr/share/java/mysql-connector-java.jar:\
10.9 /usr/share/java/glassfish-mail.jar:\
10.10 /usr/share/java/postgresql.jar
10.11 @@ -9,7 +10,7 @@
10.12 PIDFILE=/var/run/sonews.pid
10.13 ARGS="-mlgw -c /etc/sonews/sonews.conf -feed"
10.14
10.15 -MAINCLASS=org.sonews.daemon.Main
10.16 +MAINCLASS=org.sonews.Main
10.17 JAVA=java
10.18
10.19 case "$1" in
10.20 @@ -31,12 +32,18 @@
10.21 done
10.22 echo "done."
10.23 ;;
10.24 + restart)
10.25 + $0 stop && $0 start
10.26 + ;;
10.27 setup)
10.28 $JAVA -classpath $CLASSPATH org.sonews.util.DatabaseSetup
10.29 ;;
10.30 purge)
10.31 $JAVA -classpath $CLASSPATH org.sonews.util.Purger
10.32 ;;
10.33 + version)
10.34 + $JAVA -classpath $CLASSPATH $MAINCLASS -version
10.35 + ;;
10.36 *)
10.37 echo "Usage: sonews [start|stop|restart|setup|purge]"
10.38 esac
11.1 --- a/helpers/usage Wed Jul 01 10:48:22 2009 +0200
11.2 +++ b/helpers/usage Wed Jul 22 14:04:05 2009 +0200
11.3 @@ -5,4 +5,5 @@
11.4 -feed Enables feed daemon for pulling news from peer servers
11.5 -h|-help This output
11.6 -mlgw Enables the Mailinglist Gateway poller
11.7 - -useaux Enables an additional secondary port for listening
11.8 + -p <port> Forces sonews to listen on the specified port.
11.9 + -v|-version Prints out the version info an exits.
12.1 --- a/makedeb Wed Jul 01 10:48:22 2009 +0200
12.2 +++ b/makedeb Wed Jul 22 14:04:05 2009 +0200
12.3 @@ -7,11 +7,10 @@
12.4 # Create JAR files; this cannot be done with SCons,
12.5 # because Scons looses inner classes.
12.6 jar -cf sonews.jar -C classes/ org/
12.7 -jar -ufe sonews.jar org.sonews.daemon.Main
12.8 +jar -ufe sonews.jar org.sonews.Main
12.9 jar -cf test.jar -C classes/ test/
12.10 jar -ufe test.jar test.TestBench
12.11 jar -cf sonews-helpers.jar helpers/
12.12 -jar -uf sonews.jar org/sonews/web/tmpl/*.tmpl
12.13
12.14 # Create faked root for packaging
12.15 sudo rm -r $PACKAGE_ROOT/
12.16 @@ -33,13 +32,5 @@
12.17 sudo rm -r $PACKAGE_ROOT
12.18 rm -r classes/
12.19
12.20 -# Create metapackage sonews-web
12.21 -PACKAGE_ROOT=sonews-web
12.22 -mkdir $PACKAGE_ROOT
12.23 -cp -r DEBIAN-web $PACKAGE_ROOT/DEBIAN
12.24 -dpkg-deb --build $PACKAGE_ROOT
12.25 -rm -r $PACKAGE_ROOT
12.26 -
12.27 # Check debs
12.28 lintian sonews.deb
12.29 -lintian sonews-web.deb
13.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
13.2 +++ b/org/sonews/Main.java Wed Jul 22 14:04:05 2009 +0200
13.3 @@ -0,0 +1,176 @@
13.4 +/*
13.5 + * SONEWS News Server
13.6 + * see AUTHORS for the list of contributors
13.7 + *
13.8 + * This program is free software: you can redistribute it and/or modify
13.9 + * it under the terms of the GNU General Public License as published by
13.10 + * the Free Software Foundation, either version 3 of the License, or
13.11 + * (at your option) any later version.
13.12 + *
13.13 + * This program is distributed in the hope that it will be useful,
13.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
13.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13.16 + * GNU General Public License for more details.
13.17 + *
13.18 + * You should have received a copy of the GNU General Public License
13.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
13.20 + */
13.21 +
13.22 +package org.sonews;
13.23 +
13.24 +import java.sql.Driver;
13.25 +import java.sql.DriverManager;
13.26 +import java.util.Enumeration;
13.27 +import java.util.Date;
13.28 +import org.sonews.config.Config;
13.29 +import org.sonews.daemon.ChannelLineBuffers;
13.30 +import org.sonews.daemon.Connections;
13.31 +import org.sonews.daemon.NNTPDaemon;
13.32 +import org.sonews.feed.FeedManager;
13.33 +import org.sonews.mlgw.MailPoller;
13.34 +import org.sonews.storage.StorageBackendException;
13.35 +import org.sonews.storage.StorageManager;
13.36 +import org.sonews.storage.StorageProvider;
13.37 +import org.sonews.util.Log;
13.38 +import org.sonews.util.Purger;
13.39 +import org.sonews.util.io.Resource;
13.40 +
13.41 +/**
13.42 + * Startup class of the daemon.
13.43 + * @author Christian Lins
13.44 + * @since sonews/0.5.0
13.45 + */
13.46 +public final class Main
13.47 +{
13.48 +
13.49 + private Main()
13.50 + {
13.51 + }
13.52 +
13.53 + /** Version information of the sonews daemon */
13.54 + public static final String VERSION = "sonews/1.0.0";
13.55 + public static final Date STARTDATE = new Date();
13.56 +
13.57 + /**
13.58 + * The main entrypoint.
13.59 + * @param args
13.60 + * @throws Exception
13.61 + */
13.62 + public static void main(String[] args) throws Exception
13.63 + {
13.64 + System.out.println(VERSION);
13.65 + Thread.currentThread().setName("Mainthread");
13.66 +
13.67 + // Command line arguments
13.68 + boolean feed = false; // Enable feeding?
13.69 + boolean mlgw = false; // Enable Mailinglist gateway?
13.70 + int port = -1;
13.71 +
13.72 + for(int n = 0; n < args.length; n++)
13.73 + {
13.74 + if(args[n].equals("-c") || args[n].equals("-config"))
13.75 + {
13.76 + Config.inst().set(Config.LEVEL_CLI, Config.CONFIGFILE, args[++n]);
13.77 + System.out.println("Using config file " + args[n]);
13.78 + }
13.79 + else if(args[n].equals("-dumpjdbcdriver"))
13.80 + {
13.81 + System.out.println("Available JDBC drivers:");
13.82 + Enumeration<Driver> drvs = DriverManager.getDrivers();
13.83 + while(drvs.hasMoreElements())
13.84 + {
13.85 + System.out.println(drvs.nextElement());
13.86 + }
13.87 + return;
13.88 + }
13.89 + else if(args[n].equals("-feed"))
13.90 + {
13.91 + feed = true;
13.92 + }
13.93 + else if(args[n].equals("-h") || args[n].equals("-help"))
13.94 + {
13.95 + printArguments();
13.96 + return;
13.97 + }
13.98 + else if(args[n].equals("-mlgw"))
13.99 + {
13.100 + mlgw = true;
13.101 + }
13.102 + else if(args[n].equals("-p"))
13.103 + {
13.104 + port = Integer.parseInt(args[++n]);
13.105 + }
13.106 + else if(args[n].equals("-v") || args[n].equals("-version"))
13.107 + {
13.108 + // Simply return as the version info is already printed above
13.109 + return;
13.110 + }
13.111 + }
13.112 +
13.113 + // Try to load the JDBCDatabase;
13.114 + // Do NOT USE BackendConfig or Log classes before this point because they require
13.115 + // a working JDBCDatabase connection.
13.116 + try
13.117 + {
13.118 + StorageProvider sprov =
13.119 + StorageManager.loadProvider("org.sonews.storage.impl.JDBCDatabaseProvider");
13.120 + StorageManager.enableProvider(sprov);
13.121 +
13.122 + // Make sure some elementary groups are existing
13.123 + if(!StorageManager.current().isGroupExisting("control"))
13.124 + {
13.125 + StorageManager.current().addGroup("control", 0);
13.126 + Log.msg("Group 'control' created.", true);
13.127 + }
13.128 + }
13.129 + catch(StorageBackendException ex)
13.130 + {
13.131 + ex.printStackTrace();
13.132 + System.err.println("Database initialization failed with " + ex.toString());
13.133 + System.err.println("Make sure you have specified the correct database" +
13.134 + " settings in sonews.conf!");
13.135 + return;
13.136 + }
13.137 +
13.138 + ChannelLineBuffers.allocateDirect();
13.139 +
13.140 + // Add shutdown hook
13.141 + Runtime.getRuntime().addShutdownHook(new ShutdownHook());
13.142 +
13.143 + // Start the listening daemon
13.144 + if(port <= 0)
13.145 + {
13.146 + port = Config.inst().get(Config.PORT, 119);
13.147 + }
13.148 + final NNTPDaemon daemon = NNTPDaemon.createInstance(port);
13.149 + daemon.start();
13.150 +
13.151 + // Start Connections purger thread...
13.152 + Connections.getInstance().start();
13.153 +
13.154 + // Start mailinglist gateway...
13.155 + if(mlgw)
13.156 + {
13.157 + new MailPoller().start();
13.158 + }
13.159 +
13.160 + // Start feeds
13.161 + if(feed)
13.162 + {
13.163 + FeedManager.startFeeding();
13.164 + }
13.165 +
13.166 + Purger purger = new Purger();
13.167 + purger.start();
13.168 +
13.169 + // Wait for main thread to exit (setDaemon(false))
13.170 + daemon.join();
13.171 + }
13.172 +
13.173 + private static void printArguments()
13.174 + {
13.175 + String usage = Resource.getAsString("helpers/usage", true);
13.176 + System.out.println(usage);
13.177 + }
13.178 +
13.179 +}
14.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
14.2 +++ b/org/sonews/ShutdownHook.java Wed Jul 22 14:04:05 2009 +0200
14.3 @@ -0,0 +1,84 @@
14.4 +/*
14.5 + * SONEWS News Server
14.6 + * see AUTHORS for the list of contributors
14.7 + *
14.8 + * This program is free software: you can redistribute it and/or modify
14.9 + * it under the terms of the GNU General Public License as published by
14.10 + * the Free Software Foundation, either version 3 of the License, or
14.11 + * (at your option) any later version.
14.12 + *
14.13 + * This program is distributed in the hope that it will be useful,
14.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
14.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14.16 + * GNU General Public License for more details.
14.17 + *
14.18 + * You should have received a copy of the GNU General Public License
14.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
14.20 + */
14.21 +
14.22 +package org.sonews;
14.23 +
14.24 +import java.sql.SQLException;
14.25 +import java.util.Map;
14.26 +import org.sonews.daemon.AbstractDaemon;
14.27 +
14.28 +/**
14.29 + * Will force all other threads to shutdown cleanly.
14.30 + * @author Christian Lins
14.31 + * @since sonews/0.5.0
14.32 + */
14.33 +class ShutdownHook extends Thread
14.34 +{
14.35 +
14.36 + /**
14.37 + * Called when the JVM exits.
14.38 + */
14.39 + @Override
14.40 + public void run()
14.41 + {
14.42 + System.out.println("sonews: Trying to shutdown all threads...");
14.43 +
14.44 + Map<Thread, StackTraceElement[]> threadsMap = Thread.getAllStackTraces();
14.45 + for(Thread thread : threadsMap.keySet())
14.46 + {
14.47 + // Interrupt the thread if it's a AbstractDaemon
14.48 + AbstractDaemon daemon;
14.49 + if(thread instanceof AbstractDaemon && thread.isAlive())
14.50 + {
14.51 + try
14.52 + {
14.53 + daemon = (AbstractDaemon)thread;
14.54 + daemon.shutdownNow();
14.55 + }
14.56 + catch(SQLException ex)
14.57 + {
14.58 + System.out.println("sonews: " + ex);
14.59 + }
14.60 + }
14.61 + }
14.62 +
14.63 + for(Thread thread : threadsMap.keySet())
14.64 + {
14.65 + AbstractDaemon daemon;
14.66 + if(thread instanceof AbstractDaemon && thread.isAlive())
14.67 + {
14.68 + daemon = (AbstractDaemon)thread;
14.69 + System.out.println("sonews: Waiting for " + daemon + " to exit...");
14.70 + try
14.71 + {
14.72 + daemon.join(500);
14.73 + }
14.74 + catch(InterruptedException ex)
14.75 + {
14.76 + System.out.println(ex.getLocalizedMessage());
14.77 + }
14.78 + }
14.79 + }
14.80 +
14.81 + // We have notified all not-sleeping AbstractDaemons of the shutdown;
14.82 + // all other threads can be simply purged on VM shutdown
14.83 +
14.84 + System.out.println("sonews: Clean shutdown.");
14.85 + }
14.86 +
14.87 +}
15.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
15.2 +++ b/org/sonews/config/AbstractConfig.java Wed Jul 22 14:04:05 2009 +0200
15.3 @@ -0,0 +1,57 @@
15.4 +/*
15.5 + * SONEWS News Server
15.6 + * see AUTHORS for the list of contributors
15.7 + *
15.8 + * This program is free software: you can redistribute it and/or modify
15.9 + * it under the terms of the GNU General Public License as published by
15.10 + * the Free Software Foundation, either version 3 of the License, or
15.11 + * (at your option) any later version.
15.12 + *
15.13 + * This program is distributed in the hope that it will be useful,
15.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
15.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15.16 + * GNU General Public License for more details.
15.17 + *
15.18 + * You should have received a copy of the GNU General Public License
15.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
15.20 + */
15.21 +
15.22 +package org.sonews.config;
15.23 +
15.24 +/**
15.25 + * Base class for Config and BootstrapConfig.
15.26 + * @author Christian Lins
15.27 + * @since sonews/0.5.0
15.28 + */
15.29 +public abstract class AbstractConfig
15.30 +{
15.31 +
15.32 + public abstract String get(String key, String defVal);
15.33 +
15.34 + public int get(final String key, final int defVal)
15.35 + {
15.36 + return Integer.parseInt(
15.37 + get(key, Integer.toString(defVal)));
15.38 + }
15.39 +
15.40 + public boolean get(String key, boolean defVal)
15.41 + {
15.42 + String val = get(key, Boolean.toString(defVal));
15.43 + return Boolean.parseBoolean(val);
15.44 + }
15.45 +
15.46 + /**
15.47 + * Returns a long config value specified via the given key.
15.48 + * @param key
15.49 + * @param defVal
15.50 + * @return
15.51 + */
15.52 + public long get(String key, long defVal)
15.53 + {
15.54 + String val = get(key, Long.toString(defVal));
15.55 + return Long.parseLong(val);
15.56 + }
15.57 +
15.58 + protected abstract void set(String key, String val);
15.59 +
15.60 +}
16.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
16.2 +++ b/org/sonews/config/BackendConfig.java Wed Jul 22 14:04:05 2009 +0200
16.3 @@ -0,0 +1,108 @@
16.4 +/*
16.5 + * SONEWS News Server
16.6 + * see AUTHORS for the list of contributors
16.7 + *
16.8 + * This program is free software: you can redistribute it and/or modify
16.9 + * it under the terms of the GNU General Public License as published by
16.10 + * the Free Software Foundation, either version 3 of the License, or
16.11 + * (at your option) any later version.
16.12 + *
16.13 + * This program is distributed in the hope that it will be useful,
16.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
16.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16.16 + * GNU General Public License for more details.
16.17 + *
16.18 + * You should have received a copy of the GNU General Public License
16.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
16.20 + */
16.21 +
16.22 +package org.sonews.config;
16.23 +
16.24 +import org.sonews.util.Log;
16.25 +import org.sonews.storage.StorageBackendException;
16.26 +import org.sonews.storage.StorageManager;
16.27 +import org.sonews.util.TimeoutMap;
16.28 +
16.29 +/**
16.30 + * Provides access to the program wide configuration that is stored within
16.31 + * the server's database.
16.32 + * @author Christian Lins
16.33 + * @since sonews/0.5.0
16.34 + */
16.35 +class BackendConfig extends AbstractConfig
16.36 +{
16.37 +
16.38 + private static BackendConfig instance = new BackendConfig();
16.39 +
16.40 + public static BackendConfig getInstance()
16.41 + {
16.42 + return instance;
16.43 + }
16.44 +
16.45 + private final TimeoutMap<String, String> values
16.46 + = new TimeoutMap<String, String>();
16.47 +
16.48 + private BackendConfig()
16.49 + {
16.50 + super();
16.51 + }
16.52 +
16.53 + /**
16.54 + * Returns the config value for the given key or the defaultValue if the
16.55 + * key is not found in config.
16.56 + * @param key
16.57 + * @param defaultValue
16.58 + * @return
16.59 + */
16.60 + @Override
16.61 + public String get(String key, String defaultValue)
16.62 + {
16.63 + try
16.64 + {
16.65 + String configValue = values.get(key);
16.66 + if(configValue == null)
16.67 + {
16.68 + configValue = StorageManager.current().getConfigValue(key);
16.69 + if(configValue == null)
16.70 + {
16.71 + return defaultValue;
16.72 + }
16.73 + else
16.74 + {
16.75 + values.put(key, configValue);
16.76 + return configValue;
16.77 + }
16.78 + }
16.79 + else
16.80 + {
16.81 + return configValue;
16.82 + }
16.83 + }
16.84 + catch(StorageBackendException ex)
16.85 + {
16.86 + Log.msg(ex.getMessage(), false);
16.87 + return defaultValue;
16.88 + }
16.89 + }
16.90 +
16.91 + /**
16.92 + * Sets the config value which is identified by the given key.
16.93 + * @param key
16.94 + * @param value
16.95 + */
16.96 + public void set(String key, String value)
16.97 + {
16.98 + values.put(key, value);
16.99 +
16.100 + try
16.101 + {
16.102 + // Write values to database
16.103 + StorageManager.current().setConfigValue(key, value);
16.104 + }
16.105 + catch(StorageBackendException ex)
16.106 + {
16.107 + ex.printStackTrace();
16.108 + }
16.109 + }
16.110 +
16.111 +}
17.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
17.2 +++ b/org/sonews/config/CommandLineConfig.java Wed Jul 22 14:04:05 2009 +0200
17.3 @@ -0,0 +1,64 @@
17.4 +/*
17.5 + * SONEWS News Server
17.6 + * see AUTHORS for the list of contributors
17.7 + *
17.8 + * This program is free software: you can redistribute it and/or modify
17.9 + * it under the terms of the GNU General Public License as published by
17.10 + * the Free Software Foundation, either version 3 of the License, or
17.11 + * (at your option) any later version.
17.12 + *
17.13 + * This program is distributed in the hope that it will be useful,
17.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
17.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17.16 + * GNU General Public License for more details.
17.17 + *
17.18 + * You should have received a copy of the GNU General Public License
17.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
17.20 + */
17.21 +
17.22 +package org.sonews.config;
17.23 +
17.24 +import java.util.Map;
17.25 +import java.util.HashMap;
17.26 +
17.27 +/**
17.28 + *
17.29 + * @author Christian Lins
17.30 + */
17.31 +class CommandLineConfig extends AbstractConfig
17.32 +{
17.33 +
17.34 + private static final CommandLineConfig instance = new CommandLineConfig();
17.35 +
17.36 + public static CommandLineConfig getInstance()
17.37 + {
17.38 + return instance;
17.39 + }
17.40 +
17.41 + private final Map<String, String> values = new HashMap<String, String>();
17.42 +
17.43 + private CommandLineConfig() {}
17.44 +
17.45 + @Override
17.46 + public String get(String key, String def)
17.47 + {
17.48 + synchronized(this.values)
17.49 + {
17.50 + if(this.values.containsKey(key))
17.51 + {
17.52 + def = this.values.get(key);
17.53 + }
17.54 + }
17.55 + return def;
17.56 + }
17.57 +
17.58 + @Override
17.59 + public void set(String key, String val)
17.60 + {
17.61 + synchronized(this.values)
17.62 + {
17.63 + this.values.put(key, val);
17.64 + }
17.65 + }
17.66 +
17.67 +}
18.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
18.2 +++ b/org/sonews/config/Config.java Wed Jul 22 14:04:05 2009 +0200
18.3 @@ -0,0 +1,178 @@
18.4 +/*
18.5 + * SONEWS News Server
18.6 + * see AUTHORS for the list of contributors
18.7 + *
18.8 + * This program is free software: you can redistribute it and/or modify
18.9 + * it under the terms of the GNU General Public License as published by
18.10 + * the Free Software Foundation, either version 3 of the License, or
18.11 + * (at your option) any later version.
18.12 + *
18.13 + * This program is distributed in the hope that it will be useful,
18.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
18.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18.16 + * GNU General Public License for more details.
18.17 + *
18.18 + * You should have received a copy of the GNU General Public License
18.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
18.20 + */
18.21 +
18.22 +package org.sonews.config;
18.23 +
18.24 +/**
18.25 + * Configuration facade class.
18.26 + * @author Christian Lins
18.27 + * @since sonews/1.0
18.28 + */
18.29 +public class Config extends AbstractConfig
18.30 +{
18.31 +
18.32 + public static final int LEVEL_CLI = 1;
18.33 + public static final int LEVEL_FILE = 2;
18.34 + public static final int LEVEL_BACKEND = 3;
18.35 +
18.36 + public static final String CONFIGFILE = "sonews.configfile";
18.37 +
18.38 + /** BackendConfig key constant. Value is the maximum article size in kilobytes. */
18.39 + public static final String ARTICLE_MAXSIZE = "sonews.article.maxsize";
18.40 +
18.41 + /** BackendConfig key constant. Value: Amount of news that are feeded per run. */
18.42 + public static final String EVENTLOG = "sonews.eventlog";
18.43 + public static final String FEED_NEWSPERRUN = "sonews.feed.newsperrun";
18.44 + public static final String FEED_PULLINTERVAL = "sonews.feed.pullinterval";
18.45 + public static final String HOSTNAME = "sonews.hostname";
18.46 + public static final String PORT = "sonews.port";
18.47 + public static final String TIMEOUT = "sonews.timeout";
18.48 + public static final String MLPOLL_DELETEUNKNOWN = "sonews.mlpoll.deleteunknown";
18.49 + public static final String MLPOLL_HOST = "sonews.mlpoll.host";
18.50 + public static final String MLPOLL_PASSWORD = "sonews.mlpoll.password";
18.51 + public static final String MLPOLL_USER = "sonews.mlpoll.user";
18.52 + public static final String MLSEND_ADDRESS = "sonews.mlsend.address";
18.53 + public static final String MLSEND_RW_FROM = "sonews.mlsend.rewrite.from";
18.54 + public static final String MLSEND_RW_SENDER = "sonews.mlsend.rewrite.sender";
18.55 + public static final String MLSEND_HOST = "sonews.mlsend.host";
18.56 + public static final String MLSEND_PASSWORD = "sonews.mlsend.password";
18.57 + public static final String MLSEND_PORT = "sonews.mlsend.port";
18.58 + public static final String MLSEND_USER = "sonews.mlsend.user";
18.59 +
18.60 + /** Key constant. If value is "true" every I/O is written to logfile
18.61 + * (which is a lot!)
18.62 + */
18.63 + public static final String DEBUG = "sonews.debug";
18.64 +
18.65 + /** Key constant. Value is classname of the JDBC driver */
18.66 + public static final String STORAGE_DBMSDRIVER = "sonews.storage.dbmsdriver";
18.67 +
18.68 + /** Key constant. Value is JDBC connect String to the database. */
18.69 + public static final String STORAGE_DATABASE = "sonews.storage.database";
18.70 +
18.71 + /** Key constant. Value is the username for the DBMS. */
18.72 + public static final String STORAGE_USER = "sonews.storage.user";
18.73 +
18.74 + /** Key constant. Value is the password for the DBMS. */
18.75 + public static final String STORAGE_PASSWORD = "sonews.storage.password";
18.76 +
18.77 + /** Key constant. Value is the name of the host which is allowed to use the
18.78 + * XDAEMON command; default: "localhost" */
18.79 + public static final String XDAEMON_HOST = "sonews.xdaemon.host";
18.80 +
18.81 + /** The config key for the filename of the logfile */
18.82 + public static final String LOGFILE = "sonews.log";
18.83 +
18.84 + public static final String[] AVAILABLE_KEYS = {
18.85 + ARTICLE_MAXSIZE,
18.86 + EVENTLOG,
18.87 + FEED_NEWSPERRUN,
18.88 + FEED_PULLINTERVAL,
18.89 + HOSTNAME,
18.90 + MLPOLL_DELETEUNKNOWN,
18.91 + MLPOLL_HOST,
18.92 + MLPOLL_PASSWORD,
18.93 + MLPOLL_USER,
18.94 + MLSEND_ADDRESS,
18.95 + MLSEND_HOST,
18.96 + MLSEND_PASSWORD,
18.97 + MLSEND_PORT,
18.98 + MLSEND_RW_FROM,
18.99 + MLSEND_RW_SENDER,
18.100 + MLSEND_USER,
18.101 + PORT,
18.102 + TIMEOUT,
18.103 + XDAEMON_HOST
18.104 + };
18.105 +
18.106 + private static Config instance = new Config();
18.107 +
18.108 + public static Config inst()
18.109 + {
18.110 + return instance;
18.111 + }
18.112 +
18.113 + private Config(){}
18.114 +
18.115 + @Override
18.116 + public String get(String key, String def)
18.117 + {
18.118 + String val = CommandLineConfig.getInstance().get(key, null);
18.119 +
18.120 + if(val == null)
18.121 + {
18.122 + val = FileConfig.getInstance().get(key, null);
18.123 + }
18.124 +
18.125 + if(val == null)
18.126 + {
18.127 + val = BackendConfig.getInstance().get(key, def);
18.128 + }
18.129 +
18.130 + return val;
18.131 + }
18.132 +
18.133 + public String get(int level, String key, String def)
18.134 + {
18.135 + switch(level)
18.136 + {
18.137 + case LEVEL_CLI:
18.138 + {
18.139 + return CommandLineConfig.getInstance().get(key, def);
18.140 + }
18.141 + case LEVEL_FILE:
18.142 + {
18.143 + return FileConfig.getInstance().get(key, def);
18.144 + }
18.145 + case LEVEL_BACKEND:
18.146 + {
18.147 + return BackendConfig.getInstance().get(key, def);
18.148 + }
18.149 + }
18.150 + return null;
18.151 + }
18.152 +
18.153 + @Override
18.154 + public void set(String key, String val)
18.155 + {
18.156 + set(LEVEL_BACKEND, key, val);
18.157 + }
18.158 +
18.159 + public void set(int level, String key, String val)
18.160 + {
18.161 + switch(level)
18.162 + {
18.163 + case LEVEL_CLI:
18.164 + {
18.165 + CommandLineConfig.getInstance().set(key, val);
18.166 + break;
18.167 + }
18.168 + case LEVEL_FILE:
18.169 + {
18.170 + FileConfig.getInstance().set(key, val);
18.171 + break;
18.172 + }
18.173 + case LEVEL_BACKEND:
18.174 + {
18.175 + BackendConfig.getInstance().set(key, val);
18.176 + break;
18.177 + }
18.178 + }
18.179 + }
18.180 +
18.181 +}
19.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
19.2 +++ b/org/sonews/config/FileConfig.java Wed Jul 22 14:04:05 2009 +0200
19.3 @@ -0,0 +1,169 @@
19.4 +/*
19.5 + * SONEWS News Server
19.6 + * see AUTHORS for the list of contributors
19.7 + *
19.8 + * This program is free software: you can redistribute it and/or modify
19.9 + * it under the terms of the GNU General Public License as published by
19.10 + * the Free Software Foundation, either version 3 of the License, or
19.11 + * (at your option) any later version.
19.12 + *
19.13 + * This program is distributed in the hope that it will be useful,
19.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
19.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19.16 + * GNU General Public License for more details.
19.17 + *
19.18 + * You should have received a copy of the GNU General Public License
19.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
19.20 + */
19.21 +
19.22 +package org.sonews.config;
19.23 +
19.24 +import java.io.FileInputStream;
19.25 +import java.io.FileNotFoundException;
19.26 +import java.io.FileOutputStream;
19.27 +import java.io.IOException;
19.28 +import java.util.Properties;
19.29 +
19.30 +/**
19.31 + * Manages the bootstrap configuration. It MUST contain all config values
19.32 + * that are needed to establish a database connection.
19.33 + * For further configuration values use the Config class instead as that class
19.34 + * stores its values within the database.
19.35 + * @author Christian Lins
19.36 + * @since sonews/0.5.0
19.37 + */
19.38 +class FileConfig extends AbstractConfig
19.39 +{
19.40 +
19.41 + private static final Properties defaultConfig = new Properties();
19.42 +
19.43 + private static FileConfig instance = null;
19.44 +
19.45 + static
19.46 + {
19.47 + // Set some default values
19.48 + defaultConfig.setProperty(Config.STORAGE_DATABASE, "jdbc:mysql://localhost/sonews");
19.49 + defaultConfig.setProperty(Config.STORAGE_DBMSDRIVER, "com.mysql.jdbc.Driver");
19.50 + defaultConfig.setProperty(Config.STORAGE_USER, "sonews_user");
19.51 + defaultConfig.setProperty(Config.STORAGE_PASSWORD, "mysecret");
19.52 + defaultConfig.setProperty(Config.DEBUG, "false");
19.53 + }
19.54 +
19.55 + /**
19.56 + * Note: this method is not thread-safe
19.57 + * @return A Config instance
19.58 + */
19.59 + public static synchronized FileConfig getInstance()
19.60 + {
19.61 + if(instance == null)
19.62 + {
19.63 + instance = new FileConfig();
19.64 + }
19.65 + return instance;
19.66 + }
19.67 +
19.68 + // Every config instance is initialized with the default values.
19.69 + private final Properties settings = (Properties)defaultConfig.clone();
19.70 +
19.71 + /**
19.72 + * Config is a singelton class with only one instance at time.
19.73 + * So the constructor is private to prevent the creation of more
19.74 + * then one Config instance.
19.75 + * @see Config.getInstance() to retrieve an instance of Config
19.76 + */
19.77 + private FileConfig()
19.78 + {
19.79 + try
19.80 + {
19.81 + // Load settings from file
19.82 + load();
19.83 + }
19.84 + catch(IOException ex)
19.85 + {
19.86 + ex.printStackTrace();
19.87 + }
19.88 + }
19.89 +
19.90 + /**
19.91 + * Loads the configuration from the config file. By default this is done
19.92 + * by the (private) constructor but it can be useful to reload the config
19.93 + * by invoking this method.
19.94 + * @throws IOException
19.95 + */
19.96 + public void load()
19.97 + throws IOException
19.98 + {
19.99 + FileInputStream in = null;
19.100 +
19.101 + try
19.102 + {
19.103 + in = new FileInputStream(
19.104 + Config.inst().get(Config.LEVEL_CLI, Config.CONFIGFILE, "sonews.conf"));
19.105 + settings.load(in);
19.106 + }
19.107 + catch (FileNotFoundException e)
19.108 + {
19.109 + // MUST NOT use Log otherwise endless loop
19.110 + System.err.println(e.getMessage());
19.111 + save();
19.112 + }
19.113 + finally
19.114 + {
19.115 + if(in != null)
19.116 + in.close();
19.117 + }
19.118 + }
19.119 +
19.120 + /**
19.121 + * Saves this Config to the config file. By default this is done
19.122 + * at program end.
19.123 + * @throws FileNotFoundException
19.124 + * @throws IOException
19.125 + */
19.126 + public void save() throws FileNotFoundException, IOException
19.127 + {
19.128 + FileOutputStream out = null;
19.129 + try
19.130 + {
19.131 + out = new FileOutputStream(
19.132 + Config.inst().get(Config.LEVEL_CLI, Config.CONFIGFILE, "sonews.conf"));
19.133 + settings.store(out, "SONEWS Config File");
19.134 + out.flush();
19.135 + }
19.136 + catch(IOException ex)
19.137 + {
19.138 + throw ex;
19.139 + }
19.140 + finally
19.141 + {
19.142 + if(out != null)
19.143 + out.close();
19.144 + }
19.145 + }
19.146 +
19.147 + /**
19.148 + * Returns the value that is stored within this config
19.149 + * identified by the given key. If the key cannot be found
19.150 + * the default value is returned.
19.151 + * @param key Key to identify the value.
19.152 + * @param def The default value that is returned if the key
19.153 + * is not found in this Config.
19.154 + * @return
19.155 + */
19.156 + @Override
19.157 + public String get(String key, String def)
19.158 + {
19.159 + return settings.getProperty(key, def);
19.160 + }
19.161 +
19.162 + /**
19.163 + * Sets the value for a given key.
19.164 + * @param key
19.165 + * @param value
19.166 + */
19.167 + public void set(final String key, final String value)
19.168 + {
19.169 + settings.setProperty(key, value);
19.170 + }
19.171 +
19.172 +}
20.1 --- a/org/sonews/daemon/AbstractDaemon.java Wed Jul 01 10:48:22 2009 +0200
20.2 +++ b/org/sonews/daemon/AbstractDaemon.java Wed Jul 22 14:04:05 2009 +0200
20.3 @@ -19,7 +19,7 @@
20.4 package org.sonews.daemon;
20.5
20.6 import java.sql.SQLException;
20.7 -import org.sonews.daemon.storage.Database;
20.8 +import org.sonews.storage.StorageManager;
20.9 import org.sonews.util.Log;
20.10
20.11 /**
20.12 @@ -56,21 +56,17 @@
20.13 }
20.14
20.15 /**
20.16 - * Marks this thread to exit soon. Closes the associated Database connection
20.17 + * Marks this thread to exit soon. Closes the associated JDBCDatabase connection
20.18 * if available.
20.19 * @throws java.sql.SQLException
20.20 */
20.21 - void shutdownNow()
20.22 + public void shutdownNow()
20.23 throws SQLException
20.24 {
20.25 synchronized(this)
20.26 {
20.27 this.isRunning = false;
20.28 - Database db = Database.getInstance(false);
20.29 - if(db != null)
20.30 - {
20.31 - db.shutdown();
20.32 - }
20.33 + StorageManager.disableProvider();
20.34 }
20.35 }
20.36
21.1 --- a/org/sonews/daemon/BootstrapConfig.java Wed Jul 01 10:48:22 2009 +0200
21.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
21.3 @@ -1,194 +0,0 @@
21.4 -/*
21.5 - * SONEWS News Server
21.6 - * see AUTHORS for the list of contributors
21.7 - *
21.8 - * This program is free software: you can redistribute it and/or modify
21.9 - * it under the terms of the GNU General Public License as published by
21.10 - * the Free Software Foundation, either version 3 of the License, or
21.11 - * (at your option) any later version.
21.12 - *
21.13 - * This program is distributed in the hope that it will be useful,
21.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
21.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21.16 - * GNU General Public License for more details.
21.17 - *
21.18 - * You should have received a copy of the GNU General Public License
21.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
21.20 - */
21.21 -
21.22 -package org.sonews.daemon;
21.23 -
21.24 -import java.io.FileInputStream;
21.25 -import java.io.FileNotFoundException;
21.26 -import java.io.FileOutputStream;
21.27 -import java.io.IOException;
21.28 -import java.util.Properties;
21.29 -import org.sonews.util.AbstractConfig;
21.30 -
21.31 -/**
21.32 - * Manages the bootstrap configuration. It MUST contain all config values
21.33 - * that are needed to establish a database connection.
21.34 - * For further configuration values use the Config class instead as that class
21.35 - * stores its values within the database.
21.36 - * @author Christian Lins
21.37 - * @since sonews/0.5.0
21.38 - */
21.39 -public final class BootstrapConfig extends AbstractConfig
21.40 -{
21.41 -
21.42 - /** Key constant. If value is "true" every I/O is written to logfile
21.43 - * (which is a lot!)
21.44 - */
21.45 - public static final String DEBUG = "sonews.debug";
21.46 -
21.47 - /** Key constant. Value is classname of the JDBC driver */
21.48 - public static final String STORAGE_DBMSDRIVER = "sonews.storage.dbmsdriver";
21.49 -
21.50 - /** Key constant. Value is JDBC connect String to the database. */
21.51 - public static final String STORAGE_DATABASE = "sonews.storage.database";
21.52 -
21.53 - /** Key constant. Value is the username for the DBMS. */
21.54 - public static final String STORAGE_USER = "sonews.storage.user";
21.55 -
21.56 - /** Key constant. Value is the password for the DBMS. */
21.57 - public static final String STORAGE_PASSWORD = "sonews.storage.password";
21.58 -
21.59 - /** Key constant. Value is the name of the host which is allowed to use the
21.60 - * XDAEMON command; default: "localhost" */
21.61 - public static final String XDAEMON_HOST = "sonews.xdaemon.host";
21.62 -
21.63 - /** The config key for the filename of the logfile */
21.64 - public static final String LOGFILE = "sonews.log";
21.65 -
21.66 - /** The filename of the config file that is loaded on startup */
21.67 - public static volatile String FILE = "sonews.conf";
21.68 -
21.69 - private static final Properties defaultConfig = new Properties();
21.70 -
21.71 - private static BootstrapConfig instance = null;
21.72 -
21.73 - static
21.74 - {
21.75 - // Set some default values
21.76 - defaultConfig.setProperty(STORAGE_DATABASE, "jdbc:mysql://localhost/sonews");
21.77 - defaultConfig.setProperty(STORAGE_DBMSDRIVER, "com.mysql.jdbc.Driver");
21.78 - defaultConfig.setProperty(STORAGE_USER, "sonews_user");
21.79 - defaultConfig.setProperty(STORAGE_PASSWORD, "mysecret");
21.80 - defaultConfig.setProperty(DEBUG, "false");
21.81 - }
21.82 -
21.83 - /**
21.84 - * Note: this method is not thread-safe
21.85 - * @return A Config instance
21.86 - */
21.87 - public static synchronized BootstrapConfig getInstance()
21.88 - {
21.89 - if(instance == null)
21.90 - {
21.91 - instance = new BootstrapConfig();
21.92 - }
21.93 - return instance;
21.94 - }
21.95 -
21.96 - // Every config instance is initialized with the default values.
21.97 - private final Properties settings = (Properties)defaultConfig.clone();
21.98 -
21.99 - /**
21.100 - * Config is a singelton class with only one instance at time.
21.101 - * So the constructor is private to prevent the creation of more
21.102 - * then one Config instance.
21.103 - * @see Config.getInstance() to retrieve an instance of Config
21.104 - */
21.105 - private BootstrapConfig()
21.106 - {
21.107 - try
21.108 - {
21.109 - // Load settings from file
21.110 - load();
21.111 - }
21.112 - catch(IOException ex)
21.113 - {
21.114 - ex.printStackTrace();
21.115 - }
21.116 - }
21.117 -
21.118 - /**
21.119 - * Loads the configuration from the config file. By default this is done
21.120 - * by the (private) constructor but it can be useful to reload the config
21.121 - * by invoking this method.
21.122 - * @throws IOException
21.123 - */
21.124 - public void load()
21.125 - throws IOException
21.126 - {
21.127 - FileInputStream in = null;
21.128 -
21.129 - try
21.130 - {
21.131 - in = new FileInputStream(FILE);
21.132 - settings.load(in);
21.133 - }
21.134 - catch (FileNotFoundException e)
21.135 - {
21.136 - // MUST NOT use Log otherwise endless loop
21.137 - System.err.println(e.getMessage());
21.138 - save();
21.139 - }
21.140 - finally
21.141 - {
21.142 - if(in != null)
21.143 - in.close();
21.144 - }
21.145 - }
21.146 -
21.147 - /**
21.148 - * Saves this Config to the config file. By default this is done
21.149 - * at program end.
21.150 - * @throws FileNotFoundException
21.151 - * @throws IOException
21.152 - */
21.153 - public void save() throws FileNotFoundException, IOException
21.154 - {
21.155 - FileOutputStream out = null;
21.156 - try
21.157 - {
21.158 - out = new FileOutputStream(FILE);
21.159 - settings.store(out, "SONEWS Config File");
21.160 - out.flush();
21.161 - }
21.162 - catch(IOException ex)
21.163 - {
21.164 - throw ex;
21.165 - }
21.166 - finally
21.167 - {
21.168 - if(out != null)
21.169 - out.close();
21.170 - }
21.171 - }
21.172 -
21.173 - /**
21.174 - * Returns the value that is stored within this config
21.175 - * identified by the given key. If the key cannot be found
21.176 - * the default value is returned.
21.177 - * @param key Key to identify the value.
21.178 - * @param def The default value that is returned if the key
21.179 - * is not found in this Config.
21.180 - * @return
21.181 - */
21.182 - public String get(String key, String def)
21.183 - {
21.184 - return settings.getProperty(key, def);
21.185 - }
21.186 -
21.187 - /**
21.188 - * Sets the value for a given key.
21.189 - * @param key
21.190 - * @param value
21.191 - */
21.192 - public void set(final String key, final String value)
21.193 - {
21.194 - settings.setProperty(key, value);
21.195 - }
21.196 -
21.197 -}
22.1 --- a/org/sonews/daemon/ChannelLineBuffers.java Wed Jul 01 10:48:22 2009 +0200
22.2 +++ b/org/sonews/daemon/ChannelLineBuffers.java Wed Jul 22 14:04:05 2009 +0200
22.3 @@ -233,10 +233,11 @@
22.4 public static void recycleBuffer(ByteBuffer buffer)
22.5 {
22.6 assert buffer != null;
22.7 - assert buffer.capacity() >= BUFFER_SIZE;
22.8
22.9 if(buffer.isDirect())
22.10 {
22.11 + assert buffer.capacity() >= BUFFER_SIZE;
22.12 +
22.13 // Add old buffers to the list of free buffers
22.14 synchronized(freeSmallBuffers)
22.15 {
23.1 --- a/org/sonews/daemon/ChannelReader.java Wed Jul 01 10:48:22 2009 +0200
23.2 +++ b/org/sonews/daemon/ChannelReader.java Wed Jul 22 14:04:05 2009 +0200
23.3 @@ -18,7 +18,6 @@
23.4
23.5 package org.sonews.daemon;
23.6
23.7 -import org.sonews.util.Log;
23.8 import java.io.IOException;
23.9 import java.nio.ByteBuffer;
23.10 import java.nio.channels.CancelledKeyException;
23.11 @@ -27,6 +26,7 @@
23.12 import java.nio.channels.SocketChannel;
23.13 import java.util.Iterator;
23.14 import java.util.Set;
23.15 +import org.sonews.util.Log;
23.16
23.17 /**
23.18 * A Thread task listening for OP_READ events from SocketChannels.
23.19 @@ -162,7 +162,7 @@
23.20
23.21 // Some bytes are available for reading
23.22 if(selKey.isValid())
23.23 - {
23.24 + {
23.25 // Lock the channel
23.26 //synchronized(socketChannel)
23.27 {
23.28 @@ -172,7 +172,13 @@
23.29 try
23.30 {
23.31 read = socketChannel.read(buf);
23.32 - }
23.33 + }
23.34 + catch(IOException ex)
23.35 + {
23.36 + // The connection was probably closed by the remote host
23.37 + // in a non-clean fashion
23.38 + Log.msg("ChannelReader.processSelectionKey(): " + ex, true);
23.39 + }
23.40 catch(Exception ex)
23.41 {
23.42 Log.msg("ChannelReader.processSelectionKey(): " + ex, false);
24.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
24.2 +++ b/org/sonews/daemon/CommandSelector.java Wed Jul 22 14:04:05 2009 +0200
24.3 @@ -0,0 +1,110 @@
24.4 +/*
24.5 + * SONEWS News Server
24.6 + * see AUTHORS for the list of contributors
24.7 + *
24.8 + * This program is free software: you can redistribute it and/or modify
24.9 + * it under the terms of the GNU General Public License as published by
24.10 + * the Free Software Foundation, either version 3 of the License, or
24.11 + * (at your option) any later version.
24.12 + *
24.13 + * This program is distributed in the hope that it will be useful,
24.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
24.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24.16 + * GNU General Public License for more details.
24.17 + *
24.18 + * You should have received a copy of the GNU General Public License
24.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
24.20 + */
24.21 +
24.22 +package org.sonews.daemon;
24.23 +
24.24 +import java.util.HashMap;
24.25 +import java.util.Map;
24.26 +import java.util.concurrent.ConcurrentHashMap;
24.27 +import org.sonews.daemon.command.Command;
24.28 +import org.sonews.daemon.command.UnsupportedCommand;
24.29 +import org.sonews.util.Log;
24.30 +import org.sonews.util.io.Resource;
24.31 +
24.32 +/**
24.33 + * Selects the correct command processing class.
24.34 + * @author Christian Lins
24.35 + */
24.36 +class CommandSelector
24.37 +{
24.38 +
24.39 + private static Map<Thread, CommandSelector> instances
24.40 + = new ConcurrentHashMap<Thread, CommandSelector>();
24.41 +
24.42 + public static CommandSelector getInstance()
24.43 + {
24.44 + CommandSelector csel = instances.get(Thread.currentThread());
24.45 + if(csel == null)
24.46 + {
24.47 + csel = new CommandSelector();
24.48 + instances.put(Thread.currentThread(), csel);
24.49 + }
24.50 + return csel;
24.51 + }
24.52 +
24.53 + private Map<String, Command> commandMapping = new HashMap<String, Command>();
24.54 + private Command unsupportedCmd = new UnsupportedCommand();
24.55 +
24.56 + private CommandSelector()
24.57 + {
24.58 + String[] classes = Resource.getAsString("helpers/commands.list", true).split("\n");
24.59 + for(String className : classes)
24.60 + {
24.61 + try
24.62 + {
24.63 + Class<?> clazz = Class.forName(className);
24.64 + Command cmd = (Command)clazz.newInstance();
24.65 + String[] cmdStrs = cmd.getSupportedCommandStrings();
24.66 + for(String cmdStr : cmdStrs)
24.67 + {
24.68 + this.commandMapping.put(cmdStr, cmd);
24.69 + }
24.70 + }
24.71 + catch(ClassNotFoundException ex)
24.72 + {
24.73 + Log.msg("Could not load command class: " + ex, false);
24.74 + }
24.75 + catch(InstantiationException ex)
24.76 + {
24.77 + Log.msg("Could not instantiate command class: " + ex, false);
24.78 + }
24.79 + catch(IllegalAccessException ex)
24.80 + {
24.81 + Log.msg("Could not access command class: " + ex, false);
24.82 + }
24.83 + }
24.84 + }
24.85 +
24.86 + public Command get(String commandName)
24.87 + {
24.88 + try
24.89 + {
24.90 + commandName = commandName.toUpperCase();
24.91 + Command cmd = this.commandMapping.get(commandName);
24.92 +
24.93 + if(cmd == null)
24.94 + {
24.95 + return this.unsupportedCmd;
24.96 + }
24.97 + else if(cmd.isStateful())
24.98 + {
24.99 + return cmd.getClass().newInstance();
24.100 + }
24.101 + else
24.102 + {
24.103 + return cmd;
24.104 + }
24.105 + }
24.106 + catch(Exception ex)
24.107 + {
24.108 + ex.printStackTrace();
24.109 + return this.unsupportedCmd;
24.110 + }
24.111 + }
24.112 +
24.113 +}
25.1 --- a/org/sonews/daemon/Config.java Wed Jul 01 10:48:22 2009 +0200
25.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
25.3 @@ -1,149 +0,0 @@
25.4 -/*
25.5 - * SONEWS News Server
25.6 - * see AUTHORS for the list of contributors
25.7 - *
25.8 - * This program is free software: you can redistribute it and/or modify
25.9 - * it under the terms of the GNU General Public License as published by
25.10 - * the Free Software Foundation, either version 3 of the License, or
25.11 - * (at your option) any later version.
25.12 - *
25.13 - * This program is distributed in the hope that it will be useful,
25.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
25.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25.16 - * GNU General Public License for more details.
25.17 - *
25.18 - * You should have received a copy of the GNU General Public License
25.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
25.20 - */
25.21 -
25.22 -package org.sonews.daemon;
25.23 -
25.24 -import org.sonews.util.Log;
25.25 -import java.sql.SQLException;
25.26 -import org.sonews.daemon.storage.Database;
25.27 -import org.sonews.util.AbstractConfig;
25.28 -import org.sonews.util.TimeoutMap;
25.29 -
25.30 -/**
25.31 - * Provides access to the program wide configuration that is stored within
25.32 - * the server's database.
25.33 - * @author Christian Lins
25.34 - * @since sonews/0.5.0
25.35 - */
25.36 -public final class Config extends AbstractConfig
25.37 -{
25.38 -
25.39 - /** Config key constant. Value is the maximum article size in kilobytes. */
25.40 - public static final String ARTICLE_MAXSIZE = "sonews.article.maxsize";
25.41 -
25.42 - /** Config key constant. Value: Amount of news that are feeded per run. */
25.43 - public static final String FEED_NEWSPERRUN = "sonews.feed.newsperrun";
25.44 - public static final String FEED_PULLINTERVAL = "sonews.feed.pullinterval";
25.45 - public static final String HOSTNAME = "sonews.hostname";
25.46 - public static final String PORT = "sonews.port";
25.47 - public static final String TIMEOUT = "sonews.timeout";
25.48 - public static final String MLPOLL_DELETEUNKNOWN = "sonews.mlpoll.deleteunknown";
25.49 - public static final String MLPOLL_HOST = "sonews.mlpoll.host";
25.50 - public static final String MLPOLL_PASSWORD = "sonews.mlpoll.password";
25.51 - public static final String MLPOLL_USER = "sonews.mlpoll.user";
25.52 - public static final String MLSEND_ADDRESS = "sonews.mlsend.address";
25.53 - public static final String MLSEND_RW_FROM = "sonews.mlsend.rewrite.from";
25.54 - public static final String MLSEND_RW_SENDER = "sonews.mlsend.rewrite.sender";
25.55 - public static final String MLSEND_HOST = "sonews.mlsend.host";
25.56 - public static final String MLSEND_PASSWORD = "sonews.mlsend.password";
25.57 - public static final String MLSEND_PORT = "sonews.mlsend.port";
25.58 - public static final String MLSEND_USER = "sonews.mlsend.user";
25.59 -
25.60 - public static final String[] AVAILABLE_KEYS = {
25.61 - Config.ARTICLE_MAXSIZE,
25.62 - Config.FEED_NEWSPERRUN,
25.63 - Config.FEED_PULLINTERVAL,
25.64 - Config.HOSTNAME,
25.65 - Config.MLPOLL_DELETEUNKNOWN,
25.66 - Config.MLPOLL_HOST,
25.67 - Config.MLPOLL_PASSWORD,
25.68 - Config.MLPOLL_USER,
25.69 - Config.MLSEND_ADDRESS,
25.70 - Config.MLSEND_HOST,
25.71 - Config.MLSEND_PASSWORD,
25.72 - Config.MLSEND_PORT,
25.73 - Config.MLSEND_RW_FROM,
25.74 - Config.MLSEND_RW_SENDER,
25.75 - Config.MLSEND_USER,
25.76 - Config.PORT,
25.77 - Config.TIMEOUT
25.78 - };
25.79 -
25.80 - private static Config instance = new Config();
25.81 -
25.82 - public static Config getInstance()
25.83 - {
25.84 - return instance;
25.85 - }
25.86 -
25.87 - private final TimeoutMap<String, String> values
25.88 - = new TimeoutMap<String, String>();
25.89 -
25.90 - private Config()
25.91 - {
25.92 - super();
25.93 - }
25.94 -
25.95 - /**
25.96 - * Returns the config value for the given key or the defaultValue if the
25.97 - * key is not found in config.
25.98 - * @param key
25.99 - * @param defaultValue
25.100 - * @return
25.101 - */
25.102 - public String get(String key, String defaultValue)
25.103 - {
25.104 - try
25.105 - {
25.106 - String configValue = values.get(key);
25.107 - if(configValue == null)
25.108 - {
25.109 - configValue = Database.getInstance().getConfigValue(key);
25.110 - if(configValue == null)
25.111 - {
25.112 - return defaultValue;
25.113 - }
25.114 - else
25.115 - {
25.116 - values.put(key, configValue);
25.117 - return configValue;
25.118 - }
25.119 - }
25.120 - else
25.121 - {
25.122 - return configValue;
25.123 - }
25.124 - }
25.125 - catch(SQLException ex)
25.126 - {
25.127 - Log.msg(ex.getMessage(), false);
25.128 - return defaultValue;
25.129 - }
25.130 - }
25.131 -
25.132 - /**
25.133 - * Sets the config value which is identified by the given key.
25.134 - * @param key
25.135 - * @param value
25.136 - */
25.137 - public void set(String key, String value)
25.138 - {
25.139 - values.put(key, value);
25.140 -
25.141 - try
25.142 - {
25.143 - // Write values to database
25.144 - Database.getInstance().setConfigValue(key, value);
25.145 - }
25.146 - catch(SQLException ex)
25.147 - {
25.148 - ex.printStackTrace();
25.149 - }
25.150 - }
25.151 -
25.152 -}
26.1 --- a/org/sonews/daemon/Connections.java Wed Jul 01 10:48:22 2009 +0200
26.2 +++ b/org/sonews/daemon/Connections.java Wed Jul 22 14:04:05 2009 +0200
26.3 @@ -18,6 +18,7 @@
26.4
26.5 package org.sonews.daemon;
26.6
26.7 +import org.sonews.config.Config;
26.8 import org.sonews.util.Log;
26.9 import java.io.IOException;
26.10 import java.net.InetSocketAddress;
26.11 @@ -37,7 +38,7 @@
26.12 * @author Christian Lins
26.13 * @since sonews/0.5.0
26.14 */
26.15 -final class Connections extends AbstractDaemon
26.16 +public final class Connections extends AbstractDaemon
26.17 {
26.18
26.19 private static final Connections instance = new Connections();
26.20 @@ -70,7 +71,7 @@
26.21 synchronized(this.connections)
26.22 {
26.23 this.connections.add(conn);
26.24 - this.connByChannel.put(conn.getChannel(), conn);
26.25 + this.connByChannel.put(conn.getSocketChannel(), conn);
26.26 }
26.27 }
26.28
26.29 @@ -95,9 +96,9 @@
26.30 for(NNTPConnection conn : this.connections)
26.31 {
26.32 assert conn != null;
26.33 - assert conn.getChannel() != null;
26.34 + assert conn.getSocketChannel() != null;
26.35
26.36 - Socket socket = conn.getChannel().socket();
26.37 + Socket socket = conn.getSocketChannel().socket();
26.38 if(socket != null)
26.39 {
26.40 InetSocketAddress sockAddr = (InetSocketAddress)socket.getRemoteSocketAddress();
26.41 @@ -123,7 +124,7 @@
26.42 {
26.43 while(isRunning())
26.44 {
26.45 - int timeoutMillis = 1000 * Config.getInstance().get(Config.TIMEOUT, 180);
26.46 + int timeoutMillis = 1000 * Config.inst().get(Config.TIMEOUT, 180);
26.47
26.48 synchronized (this.connections)
26.49 {
26.50 @@ -139,7 +140,7 @@
26.51 iter.remove();
26.52
26.53 // Close and remove the channel
26.54 - SocketChannel channel = conn.getChannel();
26.55 + SocketChannel channel = conn.getSocketChannel();
26.56 connByChannel.remove(channel);
26.57
26.58 try
27.1 --- a/org/sonews/daemon/Main.java Wed Jul 01 10:48:22 2009 +0200
27.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
27.3 @@ -1,160 +0,0 @@
27.4 -/*
27.5 - * SONEWS News Server
27.6 - * see AUTHORS for the list of contributors
27.7 - *
27.8 - * This program is free software: you can redistribute it and/or modify
27.9 - * it under the terms of the GNU General Public License as published by
27.10 - * the Free Software Foundation, either version 3 of the License, or
27.11 - * (at your option) any later version.
27.12 - *
27.13 - * This program is distributed in the hope that it will be useful,
27.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
27.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27.16 - * GNU General Public License for more details.
27.17 - *
27.18 - * You should have received a copy of the GNU General Public License
27.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
27.20 - */
27.21 -
27.22 -package org.sonews.daemon;
27.23 -
27.24 -import java.sql.Driver;
27.25 -import java.sql.DriverManager;
27.26 -import java.sql.SQLException;
27.27 -import java.util.Enumeration;
27.28 -import java.util.Date;
27.29 -import org.sonews.feed.FeedManager;
27.30 -import org.sonews.mlgw.MailPoller;
27.31 -import org.sonews.daemon.storage.Database;
27.32 -import org.sonews.util.Log;
27.33 -import org.sonews.util.io.Resource;
27.34 -
27.35 -/**
27.36 - * Startup class of the daemon.
27.37 - * @author Christian Lins
27.38 - * @since sonews/0.5.0
27.39 - */
27.40 -public final class Main
27.41 -{
27.42 -
27.43 - private Main()
27.44 - {
27.45 - }
27.46 -
27.47 - /** Version information of the sonews daemon */
27.48 - public static final String VERSION = "sonews/0.6.0beta1";
27.49 - public static final Date STARTDATE = new Date();
27.50 -
27.51 - /**
27.52 - * The main entrypoint.
27.53 - * @param args
27.54 - * @throws Exception
27.55 - */
27.56 - public static void main(String[] args) throws Exception
27.57 - {
27.58 - System.out.println(VERSION);
27.59 - Thread.currentThread().setName("Mainthread");
27.60 -
27.61 - // Command line arguments
27.62 - boolean feed = false; // Enable feeding?
27.63 - boolean mlgw = false; // Enable Mailinglist gateway?
27.64 - int port = -1;
27.65 -
27.66 - for(int n = 0; n < args.length; n++)
27.67 - {
27.68 - if(args[n].equals("-c") || args[n].equals("-config"))
27.69 - {
27.70 - BootstrapConfig.FILE = args[++n];
27.71 - System.out.println("Using config file " + args[n]);
27.72 - }
27.73 - else if(args[n].equals("-dumpjdbcdriver"))
27.74 - {
27.75 - System.out.println("Available JDBC drivers:");
27.76 - Enumeration<Driver> drvs = DriverManager.getDrivers();
27.77 - while(drvs.hasMoreElements())
27.78 - {
27.79 - System.out.println(drvs.nextElement());
27.80 - }
27.81 - return;
27.82 - }
27.83 - else if(args[n].equals("-feed"))
27.84 - {
27.85 - feed = true;
27.86 - }
27.87 - else if(args[n].equals("-h") || args[n].equals("-help"))
27.88 - {
27.89 - printArguments();
27.90 - return;
27.91 - }
27.92 - else if(args[n].equals("-mlgw"))
27.93 - {
27.94 - mlgw = true;
27.95 - }
27.96 - else if(args[n].equals("-p"))
27.97 - {
27.98 - port = Integer.parseInt(args[++n]);
27.99 - }
27.100 - }
27.101 -
27.102 - // Try to load the Database;
27.103 - // Do NOT USE Config or Log classes before this point because they require
27.104 - // a working Database connection.
27.105 - try
27.106 - {
27.107 - Database.getInstance();
27.108 -
27.109 - // Make sure some elementary groups are existing
27.110 - if(!Database.getInstance().isGroupExisting("control"))
27.111 - {
27.112 - Database.getInstance().addGroup("control", 0);
27.113 - Log.msg("Group 'control' created.", true);
27.114 - }
27.115 - }
27.116 - catch(SQLException ex)
27.117 - {
27.118 - ex.printStackTrace();
27.119 - System.err.println("Database initialization failed with " + ex.toString());
27.120 - System.err.println("Make sure you have specified the correct database" +
27.121 - " settings in sonews.conf!");
27.122 - return;
27.123 - }
27.124 -
27.125 - ChannelLineBuffers.allocateDirect();
27.126 -
27.127 - // Add shutdown hook
27.128 - Runtime.getRuntime().addShutdownHook(new ShutdownHook());
27.129 -
27.130 - // Start the listening daemon
27.131 - if(port <= 0)
27.132 - {
27.133 - port = Config.getInstance().get(Config.PORT, 119);
27.134 - }
27.135 - final NNTPDaemon daemon = NNTPDaemon.createInstance(port);
27.136 - daemon.start();
27.137 -
27.138 - // Start Connections purger thread...
27.139 - Connections.getInstance().start();
27.140 -
27.141 - // Start mailinglist gateway...
27.142 - if(mlgw)
27.143 - {
27.144 - new MailPoller().start();
27.145 - }
27.146 -
27.147 - // Start feeds
27.148 - if(feed)
27.149 - {
27.150 - FeedManager.startFeeding();
27.151 - }
27.152 -
27.153 - // Wait for main thread to exit (setDaemon(false))
27.154 - daemon.join();
27.155 - }
27.156 -
27.157 - private static void printArguments()
27.158 - {
27.159 - String usage = Resource.getAsString("helpers/usage", true);
27.160 - System.out.println(usage);
27.161 - }
27.162 -
27.163 -}
28.1 --- a/org/sonews/daemon/NNTPConnection.java Wed Jul 01 10:48:22 2009 +0200
28.2 +++ b/org/sonews/daemon/NNTPConnection.java Wed Jul 22 14:04:05 2009 +0200
28.3 @@ -21,33 +21,19 @@
28.4 import org.sonews.util.Log;
28.5 import java.io.IOException;
28.6 import java.net.InetSocketAddress;
28.7 +import java.net.SocketException;
28.8 import java.nio.ByteBuffer;
28.9 import java.nio.CharBuffer;
28.10 import java.nio.channels.ClosedChannelException;
28.11 import java.nio.channels.SelectionKey;
28.12 import java.nio.channels.SocketChannel;
28.13 import java.nio.charset.Charset;
28.14 +import java.util.Arrays;
28.15 import java.util.Timer;
28.16 import java.util.TimerTask;
28.17 -import org.sonews.daemon.command.ArticleCommand;
28.18 -import org.sonews.daemon.command.CapabilitiesCommand;
28.19 -import org.sonews.daemon.command.AbstractCommand;
28.20 -import org.sonews.daemon.command.GroupCommand;
28.21 -import org.sonews.daemon.command.HelpCommand;
28.22 -import org.sonews.daemon.command.ListCommand;
28.23 -import org.sonews.daemon.command.ListGroupCommand;
28.24 -import org.sonews.daemon.command.ModeReaderCommand;
28.25 -import org.sonews.daemon.command.NewGroupsCommand;
28.26 -import org.sonews.daemon.command.NextPrevCommand;
28.27 -import org.sonews.daemon.command.OverCommand;
28.28 -import org.sonews.daemon.command.PostCommand;
28.29 -import org.sonews.daemon.command.QuitCommand;
28.30 -import org.sonews.daemon.command.StatCommand;
28.31 -import org.sonews.daemon.command.UnsupportedCommand;
28.32 -import org.sonews.daemon.command.XDaemonCommand;
28.33 -import org.sonews.daemon.command.XPatCommand;
28.34 -import org.sonews.daemon.storage.Article;
28.35 -import org.sonews.daemon.storage.Group;
28.36 +import org.sonews.daemon.command.Command;
28.37 +import org.sonews.storage.Article;
28.38 +import org.sonews.storage.Channel;
28.39 import org.sonews.util.Stats;
28.40
28.41 /**
28.42 @@ -67,9 +53,9 @@
28.43 /** SocketChannel is generally thread-safe */
28.44 private SocketChannel channel = null;
28.45 private Charset charset = Charset.forName("UTF-8");
28.46 - private AbstractCommand command = null;
28.47 + private Command command = null;
28.48 private Article currentArticle = null;
28.49 - private Group currentGroup = null;
28.50 + private Channel currentGroup = null;
28.51 private volatile long lastActivity = System.currentTimeMillis();
28.52 private ChannelLineBuffers lineBuffers = new ChannelLineBuffers();
28.53 private int readLock = 0;
28.54 @@ -201,6 +187,11 @@
28.55 channel.socket().shutdownOutput();
28.56 channel.close();
28.57 }
28.58 + catch(SocketException ex)
28.59 + {
28.60 + // Socket was already disconnected
28.61 + Log.msg("NNTPConnection.shutdownOutput(): " + ex, true);
28.62 + }
28.63 catch(Exception ex)
28.64 {
28.65 Log.msg("NNTPConnection.shutdownOutput(): " + ex, false);
28.66 @@ -213,7 +204,7 @@
28.67 }, 3000);
28.68 }
28.69
28.70 - public SocketChannel getChannel()
28.71 + public SocketChannel getSocketChannel()
28.72 {
28.73 return this.channel;
28.74 }
28.75 @@ -227,8 +218,11 @@
28.76 {
28.77 return this.charset;
28.78 }
28.79 -
28.80 - public Group getCurrentGroup()
28.81 +
28.82 + /**
28.83 + * @return The currently selected communication channel (not SocketChannel)
28.84 + */
28.85 + public Channel getCurrentChannel()
28.86 {
28.87 return this.currentGroup;
28.88 }
28.89 @@ -238,7 +232,7 @@
28.90 this.currentArticle = article;
28.91 }
28.92
28.93 - public void setCurrentGroup(final Group group)
28.94 + public void setCurrentGroup(final Channel group)
28.95 {
28.96 this.currentGroup = group;
28.97 }
28.98 @@ -275,6 +269,7 @@
28.99 if(line.endsWith("\r"))
28.100 {
28.101 line = line.substring(0, line.length() - 1);
28.102 + raw = Arrays.copyOf(raw, raw.length - 1);
28.103 }
28.104
28.105 Log.msg("<< " + line, true);
28.106 @@ -288,7 +283,7 @@
28.107 try
28.108 {
28.109 // The command object will process the line we just received
28.110 - command.processLine(line);
28.111 + command.processLine(this, line, raw);
28.112 }
28.113 catch(ClosedChannelException ex0)
28.114 {
28.115 @@ -324,86 +319,14 @@
28.116 }
28.117
28.118 /**
28.119 - * This method performes several if/elseif constructs to determine the
28.120 - * fitting command object.
28.121 - * TODO: This string comparisons are probably slow!
28.122 + * This method determines the fitting command processing class.
28.123 * @param line
28.124 * @return
28.125 */
28.126 - private AbstractCommand parseCommandLine(String line)
28.127 + private Command parseCommandLine(String line)
28.128 {
28.129 - AbstractCommand cmd = new UnsupportedCommand(this);
28.130 - String cmdStr = line.split(" ")[0];
28.131 -
28.132 - if(cmdStr.equalsIgnoreCase("ARTICLE") ||
28.133 - cmdStr.equalsIgnoreCase("BODY"))
28.134 - {
28.135 - cmd = new ArticleCommand(this);
28.136 - }
28.137 - else if(cmdStr.equalsIgnoreCase("CAPABILITIES"))
28.138 - {
28.139 - cmd = new CapabilitiesCommand(this);
28.140 - }
28.141 - else if(cmdStr.equalsIgnoreCase("GROUP"))
28.142 - {
28.143 - cmd = new GroupCommand(this);
28.144 - }
28.145 - else if(cmdStr.equalsIgnoreCase("HEAD"))
28.146 - {
28.147 - cmd = new ArticleCommand(this);
28.148 - }
28.149 - else if(cmdStr.equalsIgnoreCase("HELP"))
28.150 - {
28.151 - cmd = new HelpCommand(this);
28.152 - }
28.153 - else if(cmdStr.equalsIgnoreCase("LIST"))
28.154 - {
28.155 - cmd = new ListCommand(this);
28.156 - }
28.157 - else if(cmdStr.equalsIgnoreCase("LISTGROUP"))
28.158 - {
28.159 - cmd = new ListGroupCommand(this);
28.160 - }
28.161 - else if(cmdStr.equalsIgnoreCase("MODE"))
28.162 - {
28.163 - cmd = new ModeReaderCommand(this);
28.164 - }
28.165 - else if(cmdStr.equalsIgnoreCase("NEWGROUPS"))
28.166 - {
28.167 - cmd = new NewGroupsCommand(this);
28.168 - }
28.169 - else if(cmdStr.equalsIgnoreCase("NEXT") ||
28.170 - cmdStr.equalsIgnoreCase("PREV"))
28.171 - {
28.172 - cmd = new NextPrevCommand(this);
28.173 - }
28.174 - else if(cmdStr.equalsIgnoreCase("OVER") ||
28.175 - cmdStr.equalsIgnoreCase("XOVER")) // for compatibility with older RFCs
28.176 - {
28.177 - cmd = new OverCommand(this);
28.178 - }
28.179 - else if(cmdStr.equalsIgnoreCase("POST"))
28.180 - {
28.181 - cmd = new PostCommand(this);
28.182 - }
28.183 - else if(cmdStr.equalsIgnoreCase("QUIT"))
28.184 - {
28.185 - cmd = new QuitCommand(this);
28.186 - }
28.187 - else if(cmdStr.equalsIgnoreCase("STAT"))
28.188 - {
28.189 - cmd = new StatCommand(this);
28.190 - }
28.191 - else if(cmdStr.equalsIgnoreCase("XDAEMON"))
28.192 - {
28.193 - cmd = new XDaemonCommand(this);
28.194 - }
28.195 - else if(cmdStr.equalsIgnoreCase("XPAT"))
28.196 - {
28.197 - cmd = new XPatCommand(this);
28.198 - }
28.199 -
28.200 - return cmd;
28.201 + String cmdStr = line.split(" ")[0];
28.202 + return CommandSelector.getInstance().get(cmdStr);
28.203 }
28.204
28.205 /**
28.206 @@ -419,6 +342,18 @@
28.207 writeToChannel(CharBuffer.wrap(line), charset, line);
28.208 writeToChannel(CharBuffer.wrap(NEWLINE), charset, null);
28.209 }
28.210 +
28.211 + /**
28.212 + * Writes the given raw lines to the output buffers and finishes with
28.213 + * a newline character (\r\n).
28.214 + * @param rawLines
28.215 + */
28.216 + public void println(final byte[] rawLines)
28.217 + throws IOException
28.218 + {
28.219 + this.lineBuffers.addOutputBuffer(ByteBuffer.wrap(rawLines));
28.220 + writeToChannel(CharBuffer.wrap(NEWLINE), charset, null);
28.221 + }
28.222
28.223 /**
28.224 * Encodes the given CharBuffer using the given Charset to a bunch of
28.225 @@ -440,6 +375,11 @@
28.226 LineEncoder lenc = new LineEncoder(characters, charset);
28.227 lenc.encode(lineBuffers);
28.228
28.229 + enableWriteEvents(debugLine);
28.230 + }
28.231 +
28.232 + private void enableWriteEvents(CharSequence debugLine)
28.233 + {
28.234 // Enable OP_WRITE events so that the buffers are processed
28.235 try
28.236 {
29.1 --- a/org/sonews/daemon/NNTPDaemon.java Wed Jul 01 10:48:22 2009 +0200
29.2 +++ b/org/sonews/daemon/NNTPDaemon.java Wed Jul 22 14:04:05 2009 +0200
29.3 @@ -18,6 +18,8 @@
29.4
29.5 package org.sonews.daemon;
29.6
29.7 +import org.sonews.config.Config;
29.8 +import org.sonews.Main;
29.9 import org.sonews.util.Log;
29.10 import java.io.IOException;
29.11 import java.net.BindException;
29.12 @@ -140,7 +142,7 @@
29.13
29.14 // Set write selection key and send hello to client
29.15 conn.setWriteSelectionKey(selKeyWrite);
29.16 - conn.println("200 " + Config.getInstance().get(Config.HOSTNAME, "localhost")
29.17 + conn.println("200 " + Config.inst().get(Config.HOSTNAME, "localhost")
29.18 + " " + Main.VERSION + " news server ready - (posting ok).");
29.19 }
29.20 catch(CancelledKeyException cke)
30.1 --- a/org/sonews/daemon/ShutdownHook.java Wed Jul 01 10:48:22 2009 +0200
30.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
30.3 @@ -1,83 +0,0 @@
30.4 -/*
30.5 - * SONEWS News Server
30.6 - * see AUTHORS for the list of contributors
30.7 - *
30.8 - * This program is free software: you can redistribute it and/or modify
30.9 - * it under the terms of the GNU General Public License as published by
30.10 - * the Free Software Foundation, either version 3 of the License, or
30.11 - * (at your option) any later version.
30.12 - *
30.13 - * This program is distributed in the hope that it will be useful,
30.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
30.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30.16 - * GNU General Public License for more details.
30.17 - *
30.18 - * You should have received a copy of the GNU General Public License
30.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
30.20 - */
30.21 -
30.22 -package org.sonews.daemon;
30.23 -
30.24 -import java.sql.SQLException;
30.25 -import java.util.Map;
30.26 -
30.27 -/**
30.28 - * Will force all other threads to shutdown cleanly.
30.29 - * @author Christian Lins
30.30 - * @since sonews/0.5.0
30.31 - */
30.32 -class ShutdownHook extends Thread
30.33 -{
30.34 -
30.35 - /**
30.36 - * Called when the JVM exits.
30.37 - */
30.38 - @Override
30.39 - public void run()
30.40 - {
30.41 - System.out.println("sonews: Trying to shutdown all threads...");
30.42 -
30.43 - Map<Thread, StackTraceElement[]> threadsMap = Thread.getAllStackTraces();
30.44 - for(Thread thread : threadsMap.keySet())
30.45 - {
30.46 - // Interrupt the thread if it's a AbstractDaemon
30.47 - AbstractDaemon daemon;
30.48 - if(thread instanceof AbstractDaemon && thread.isAlive())
30.49 - {
30.50 - try
30.51 - {
30.52 - daemon = (AbstractDaemon)thread;
30.53 - daemon.shutdownNow();
30.54 - }
30.55 - catch(SQLException ex)
30.56 - {
30.57 - System.out.println("sonews: " + ex);
30.58 - }
30.59 - }
30.60 - }
30.61 -
30.62 - for(Thread thread : threadsMap.keySet())
30.63 - {
30.64 - AbstractDaemon daemon;
30.65 - if(thread instanceof AbstractDaemon && thread.isAlive())
30.66 - {
30.67 - daemon = (AbstractDaemon)thread;
30.68 - System.out.println("sonews: Waiting for " + daemon + " to exit...");
30.69 - try
30.70 - {
30.71 - daemon.join(500);
30.72 - }
30.73 - catch(InterruptedException ex)
30.74 - {
30.75 - System.out.println(ex.getLocalizedMessage());
30.76 - }
30.77 - }
30.78 - }
30.79 -
30.80 - // We have notified all not-sleeping AbstractDaemons of the shutdown;
30.81 - // all other threads can be simply purged on VM shutdown
30.82 -
30.83 - System.out.println("sonews: Clean shutdown.");
30.84 - }
30.85 -
30.86 -}
31.1 --- a/org/sonews/daemon/command/AbstractCommand.java Wed Jul 01 10:48:22 2009 +0200
31.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
31.3 @@ -1,87 +0,0 @@
31.4 -/*
31.5 - * SONEWS News Server
31.6 - * see AUTHORS for the list of contributors
31.7 - *
31.8 - * This program is free software: you can redistribute it and/or modify
31.9 - * it under the terms of the GNU General Public License as published by
31.10 - * the Free Software Foundation, either version 3 of the License, or
31.11 - * (at your option) any later version.
31.12 - *
31.13 - * This program is distributed in the hope that it will be useful,
31.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
31.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31.16 - * GNU General Public License for more details.
31.17 - *
31.18 - * You should have received a copy of the GNU General Public License
31.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
31.20 - */
31.21 -
31.22 -package org.sonews.daemon.command;
31.23 -
31.24 -import java.io.IOException;
31.25 -import java.nio.charset.Charset;
31.26 -import java.sql.SQLException;
31.27 -import org.sonews.daemon.NNTPConnection;
31.28 -import org.sonews.daemon.storage.Article;
31.29 -import org.sonews.daemon.storage.Group;
31.30 -
31.31 -/**
31.32 - * Base class for all command handling classes.
31.33 - * @author Christian Lins
31.34 - * @author Dennis Schwerdel
31.35 - * @since n3tpd/0.1
31.36 - */
31.37 -public abstract class AbstractCommand
31.38 -{
31.39 -
31.40 - protected NNTPConnection connection;
31.41 -
31.42 - public AbstractCommand(final NNTPConnection connection)
31.43 - {
31.44 - this.connection = connection;
31.45 - }
31.46 -
31.47 - protected Article getCurrentArticle()
31.48 - {
31.49 - return connection.getCurrentArticle();
31.50 - }
31.51 -
31.52 - protected Group getCurrentGroup()
31.53 - {
31.54 - return connection.getCurrentGroup();
31.55 - }
31.56 -
31.57 - protected void setCurrentArticle(final Article current)
31.58 - {
31.59 - connection.setCurrentArticle(current);
31.60 - }
31.61 -
31.62 - protected void setCurrentGroup(final Group current)
31.63 - {
31.64 - connection.setCurrentGroup(current);
31.65 - }
31.66 -
31.67 - public abstract void processLine(String line)
31.68 - throws IOException, SQLException;
31.69 -
31.70 - protected void println(final String line)
31.71 - throws IOException
31.72 - {
31.73 - connection.println(line);
31.74 - }
31.75 -
31.76 - protected void println(final String line, final Charset charset)
31.77 - throws IOException
31.78 - {
31.79 - connection.println(line, charset);
31.80 - }
31.81 -
31.82 - protected void printStatus(final int status, final String msg)
31.83 - throws IOException
31.84 - {
31.85 - println(status + " " + msg);
31.86 - }
31.87 -
31.88 - public abstract boolean hasFinished();
31.89 -
31.90 -}
32.1 --- a/org/sonews/daemon/command/ArticleCommand.java Wed Jul 01 10:48:22 2009 +0200
32.2 +++ b/org/sonews/daemon/command/ArticleCommand.java Wed Jul 22 14:04:05 2009 +0200
32.3 @@ -19,10 +19,10 @@
32.4 package org.sonews.daemon.command;
32.5
32.6 import java.io.IOException;
32.7 -import java.sql.SQLException;
32.8 -import org.sonews.daemon.storage.Article;
32.9 +import org.sonews.storage.Article;
32.10 import org.sonews.daemon.NNTPConnection;
32.11 -import org.sonews.daemon.storage.Group;
32.12 +import org.sonews.storage.Channel;
32.13 +import org.sonews.storage.StorageBackendException;
32.14
32.15 /**
32.16 * Class handling the ARTICLE, BODY and HEAD commands.
32.17 @@ -30,12 +30,13 @@
32.18 * @author Dennis Schwerdel
32.19 * @since n3tpd/0.1
32.20 */
32.21 -public class ArticleCommand extends AbstractCommand
32.22 +public class ArticleCommand implements Command
32.23 {
32.24 -
32.25 - public ArticleCommand(final NNTPConnection connection)
32.26 +
32.27 + @Override
32.28 + public String[] getSupportedCommandStrings()
32.29 {
32.30 - super(connection);
32.31 + return new String[] {"ARTICLE", "BODY", "HEAD"};
32.32 }
32.33
32.34 @Override
32.35 @@ -44,9 +45,15 @@
32.36 return true;
32.37 }
32.38
32.39 + @Override
32.40 + public boolean isStateful()
32.41 + {
32.42 + return false;
32.43 + }
32.44 +
32.45 // TODO: Refactor this method to reduce its complexity!
32.46 @Override
32.47 - public void processLine(final String line)
32.48 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
32.49 throws IOException
32.50 {
32.51 final String[] command = line.split(" ");
32.52 @@ -55,10 +62,10 @@
32.53 long artIndex = -1;
32.54 if (command.length == 1)
32.55 {
32.56 - article = getCurrentArticle();
32.57 + article = conn.getCurrentArticle();
32.58 if (article == null)
32.59 {
32.60 - printStatus(420, "no current article has been selected");
32.61 + conn.println("420 no current article has been selected");
32.62 return;
32.63 }
32.64 }
32.65 @@ -68,7 +75,7 @@
32.66 article = Article.getByMessageID(command[1]);
32.67 if (article == null)
32.68 {
32.69 - printStatus(430, "no such article found");
32.70 + conn.println("430 no such article found");
32.71 return;
32.72 }
32.73 }
32.74 @@ -77,49 +84,47 @@
32.75 // Message Number
32.76 try
32.77 {
32.78 - Group currentGroup = connection.getCurrentGroup();
32.79 + Channel currentGroup = conn.getCurrentChannel();
32.80 if(currentGroup == null)
32.81 {
32.82 - printStatus(400, "no group selected");
32.83 + conn.println("400 no group selected");
32.84 return;
32.85 }
32.86
32.87 artIndex = Long.parseLong(command[1]);
32.88 - article = Article.getByArticleNumber(artIndex, currentGroup);
32.89 + article = currentGroup.getArticle(artIndex);
32.90 }
32.91 catch(NumberFormatException ex)
32.92 {
32.93 ex.printStackTrace();
32.94 }
32.95 - catch(SQLException ex)
32.96 + catch(StorageBackendException ex)
32.97 {
32.98 ex.printStackTrace();
32.99 }
32.100
32.101 if (article == null)
32.102 {
32.103 - printStatus(423, "no such article number in this group");
32.104 + conn.println("423 no such article number in this group");
32.105 return;
32.106 }
32.107 - setCurrentArticle(article);
32.108 + conn.setCurrentArticle(article);
32.109 }
32.110
32.111 if(command[0].equalsIgnoreCase("ARTICLE"))
32.112 {
32.113 - printStatus(220, artIndex + " " + article.getMessageID()
32.114 + conn.println("220 " + artIndex + " " + article.getMessageID()
32.115 + " article retrieved - head and body follow");
32.116 -
32.117 - println(article.getHeaderSource());
32.118 -
32.119 - println("");
32.120 - println(article.getBody(), article.getBodyCharset());
32.121 - println(".");
32.122 + conn.println(article.getHeaderSource());
32.123 + conn.println("");
32.124 + conn.println(article.getBody());
32.125 + conn.println(".");
32.126 }
32.127 else if(command[0].equalsIgnoreCase("BODY"))
32.128 {
32.129 - printStatus(222, artIndex + " " + article.getMessageID() + " body");
32.130 - println(article.getBody(), article.getBodyCharset());
32.131 - println(".");
32.132 + conn.println("222 " + artIndex + " " + article.getMessageID() + " body");
32.133 + conn.println(article.getBody());
32.134 + conn.println(".");
32.135 }
32.136
32.137 /*
32.138 @@ -153,11 +158,10 @@
32.139 */
32.140 else if(command[0].equalsIgnoreCase("HEAD"))
32.141 {
32.142 - printStatus(221, artIndex + " " + article.getMessageID()
32.143 + conn.println("221 " + artIndex + " " + article.getMessageID()
32.144 + " Headers follow (multi-line)");
32.145 -
32.146 - println(article.getHeaderSource());
32.147 - println(".");
32.148 + conn.println(article.getHeaderSource());
32.149 + conn.println(".");
32.150 }
32.151 }
32.152
33.1 --- a/org/sonews/daemon/command/CapabilitiesCommand.java Wed Jul 01 10:48:22 2009 +0200
33.2 +++ b/org/sonews/daemon/command/CapabilitiesCommand.java Wed Jul 22 14:04:05 2009 +0200
33.3 @@ -39,20 +39,21 @@
33.4 * @author Christian Lins
33.5 * @since sonews/0.5.0
33.6 */
33.7 -public class CapabilitiesCommand extends AbstractCommand
33.8 +public class CapabilitiesCommand implements Command
33.9 {
33.10
33.11 - protected static final String[] CAPABILITIES = new String[]
33.12 + static final String[] CAPABILITIES = new String[]
33.13 {
33.14 "VERSION 2", // MUST be the first one; VERSION 2 refers to RFC3977
33.15 "READER", // Server implements commands for reading
33.16 "POST", // Server implements POST command
33.17 "OVER" // Server implements OVER command
33.18 };
33.19 -
33.20 - public CapabilitiesCommand(final NNTPConnection conn)
33.21 +
33.22 + @Override
33.23 + public String[] getSupportedCommandStrings()
33.24 {
33.25 - super(conn);
33.26 + return new String[] {"CAPABILITIES"};
33.27 }
33.28
33.29 /**
33.30 @@ -66,15 +67,21 @@
33.31 }
33.32
33.33 @Override
33.34 - public void processLine(final String line)
33.35 + public boolean isStateful()
33.36 + {
33.37 + return false;
33.38 + }
33.39 +
33.40 + @Override
33.41 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
33.42 throws IOException
33.43 {
33.44 - printStatus(101, "Capabilities list:");
33.45 + conn.println("101 Capabilities list:");
33.46 for(String cap : CAPABILITIES)
33.47 {
33.48 - println(cap);
33.49 + conn.println(cap);
33.50 }
33.51 - println(".");
33.52 + conn.println(".");
33.53 }
33.54
33.55 }
34.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
34.2 +++ b/org/sonews/daemon/command/Command.java Wed Jul 22 14:04:05 2009 +0200
34.3 @@ -0,0 +1,42 @@
34.4 +/*
34.5 + * SONEWS News Server
34.6 + * see AUTHORS for the list of contributors
34.7 + *
34.8 + * This program is free software: you can redistribute it and/or modify
34.9 + * it under the terms of the GNU General Public License as published by
34.10 + * the Free Software Foundation, either version 3 of the License, or
34.11 + * (at your option) any later version.
34.12 + *
34.13 + * This program is distributed in the hope that it will be useful,
34.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
34.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34.16 + * GNU General Public License for more details.
34.17 + *
34.18 + * You should have received a copy of the GNU General Public License
34.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
34.20 + */
34.21 +
34.22 +package org.sonews.daemon.command;
34.23 +
34.24 +import java.io.IOException;
34.25 +import org.sonews.daemon.NNTPConnection;
34.26 +import org.sonews.storage.StorageBackendException;
34.27 +
34.28 +/**
34.29 + * Interface for pluggable NNTP commands handling classes.
34.30 + * @author Christian Lins
34.31 + * @since sonews/0.6.0
34.32 + */
34.33 +public interface Command
34.34 +{
34.35 +
34.36 + boolean hasFinished();
34.37 +
34.38 + boolean isStateful();
34.39 +
34.40 + String[] getSupportedCommandStrings();
34.41 +
34.42 + void processLine(NNTPConnection conn, String line, byte[] rawLine)
34.43 + throws IOException, StorageBackendException;
34.44 +
34.45 +}
35.1 --- a/org/sonews/daemon/command/GroupCommand.java Wed Jul 01 10:48:22 2009 +0200
35.2 +++ b/org/sonews/daemon/command/GroupCommand.java Wed Jul 22 14:04:05 2009 +0200
35.3 @@ -19,9 +19,9 @@
35.4 package org.sonews.daemon.command;
35.5
35.6 import java.io.IOException;
35.7 -import java.sql.SQLException;
35.8 import org.sonews.daemon.NNTPConnection;
35.9 -import org.sonews.daemon.storage.Group;
35.10 +import org.sonews.storage.Channel;
35.11 +import org.sonews.storage.StorageBackendException;
35.12
35.13 /**
35.14 * Class handling the GROUP command.
35.15 @@ -45,12 +45,13 @@
35.16 * @author Dennis Schwerdel
35.17 * @since n3tpd/0.1
35.18 */
35.19 -public class GroupCommand extends AbstractCommand
35.20 +public class GroupCommand implements Command
35.21 {
35.22
35.23 - public GroupCommand(final NNTPConnection conn)
35.24 + @Override
35.25 + public String[] getSupportedCommandStrings()
35.26 {
35.27 - super(conn);
35.28 + return new String[]{"GROUP"};
35.29 }
35.30
35.31 @Override
35.32 @@ -58,32 +59,37 @@
35.33 {
35.34 return true;
35.35 }
35.36 +
35.37 + @Override
35.38 + public boolean isStateful()
35.39 + {
35.40 + return true;
35.41 + }
35.42
35.43 @Override
35.44 - public void processLine(final String line)
35.45 - throws IOException, SQLException
35.46 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
35.47 + throws IOException, StorageBackendException
35.48 {
35.49 final String[] command = line.split(" ");
35.50
35.51 - Group group;
35.52 + Channel group;
35.53 if(command.length >= 2)
35.54 {
35.55 - group = Group.getByName(command[1]);
35.56 - if(group == null)
35.57 + group = Channel.getByName(command[1]);
35.58 + if(group == null || group.isDeleted())
35.59 {
35.60 - printStatus(411, "no such news group");
35.61 + conn.println("411 no such news group");
35.62 }
35.63 else
35.64 {
35.65 - setCurrentGroup(group);
35.66 -
35.67 - printStatus(211, group.getPostingsCount() + " " + group.getFirstArticleNumber()
35.68 + conn.setCurrentGroup(group);
35.69 + conn.println("211 " + group.getPostingsCount() + " " + group.getFirstArticleNumber()
35.70 + " " + group.getLastArticleNumber() + " " + group.getName() + " group selected");
35.71 }
35.72 }
35.73 else
35.74 {
35.75 - printStatus(500, "no group name given");
35.76 + conn.println("500 no group name given");
35.77 }
35.78 }
35.79
36.1 --- a/org/sonews/daemon/command/HelpCommand.java Wed Jul 01 10:48:22 2009 +0200
36.2 +++ b/org/sonews/daemon/command/HelpCommand.java Wed Jul 22 14:04:05 2009 +0200
36.3 @@ -30,34 +30,41 @@
36.4 * @author Christian Lins
36.5 * @since sonews/0.5.0
36.6 */
36.7 -public class HelpCommand extends AbstractCommand
36.8 +public class HelpCommand implements Command
36.9 {
36.10 -
36.11 - public HelpCommand(final NNTPConnection conn)
36.12 - {
36.13 - super(conn);
36.14 - }
36.15
36.16 @Override
36.17 public boolean hasFinished()
36.18 {
36.19 return true;
36.20 }
36.21 +
36.22 + @Override
36.23 + public boolean isStateful()
36.24 + {
36.25 + return true;
36.26 + }
36.27 +
36.28 + @Override
36.29 + public String[] getSupportedCommandStrings()
36.30 + {
36.31 + return new String[]{"HELP"};
36.32 + }
36.33
36.34 @Override
36.35 - public void processLine(final String line)
36.36 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
36.37 throws IOException
36.38 {
36.39 - printStatus(100, "help text follows");
36.40 + conn.println("100 help text follows");
36.41
36.42 final String[] help = Resource
36.43 .getAsString("helpers/helptext", true).split("\n");
36.44 for(String hstr : help)
36.45 {
36.46 - println(hstr);
36.47 + conn.println(hstr);
36.48 }
36.49
36.50 - println(".");
36.51 + conn.println(".");
36.52 }
36.53
36.54 }
37.1 --- a/org/sonews/daemon/command/ListCommand.java Wed Jul 01 10:48:22 2009 +0200
37.2 +++ b/org/sonews/daemon/command/ListCommand.java Wed Jul 22 14:04:05 2009 +0200
37.3 @@ -19,10 +19,10 @@
37.4 package org.sonews.daemon.command;
37.5
37.6 import java.io.IOException;
37.7 -import java.sql.SQLException;
37.8 import java.util.List;
37.9 import org.sonews.daemon.NNTPConnection;
37.10 -import org.sonews.daemon.storage.Group;
37.11 +import org.sonews.storage.Channel;
37.12 +import org.sonews.storage.StorageBackendException;
37.13
37.14 /**
37.15 * Class handling the LIST command.
37.16 @@ -30,12 +30,13 @@
37.17 * @author Dennis Schwerdel
37.18 * @since n3tpd/0.1
37.19 */
37.20 -public class ListCommand extends AbstractCommand
37.21 +public class ListCommand implements Command
37.22 {
37.23
37.24 - public ListCommand(final NNTPConnection conn)
37.25 + @Override
37.26 + public String[] getSupportedCommandStrings()
37.27 {
37.28 - super(conn);
37.29 + return new String[]{"LIST"};
37.30 }
37.31
37.32 @Override
37.33 @@ -43,10 +44,16 @@
37.34 {
37.35 return true;
37.36 }
37.37 +
37.38 + @Override
37.39 + public boolean isStateful()
37.40 + {
37.41 + return false;
37.42 + }
37.43
37.44 @Override
37.45 - public void processLine(final String line)
37.46 - throws IOException, SQLException
37.47 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
37.48 + throws IOException, StorageBackendException
37.49 {
37.50 final String[] command = line.split(" ");
37.51
37.52 @@ -54,54 +61,59 @@
37.53 {
37.54 if (command[1].equalsIgnoreCase("OVERVIEW.FMT"))
37.55 {
37.56 - printStatus(215, "information follows");
37.57 - println("Subject:\nFrom:\nDate:\nMessage-ID:\nReferences:\nBytes:\nLines:\nXref");
37.58 - println(".");
37.59 + conn.println("215 information follows");
37.60 + conn.println("Subject:\nFrom:\nDate:\nMessage-ID:\nReferences:\nBytes:\nLines:\nXref");
37.61 + conn.println(".");
37.62 }
37.63 else if (command[1].equalsIgnoreCase("NEWSGROUPS"))
37.64 {
37.65 - printStatus(215, "information follows");
37.66 - final List<Group> list = Group.getAll();
37.67 - for (Group g : list)
37.68 + conn.println("215 information follows");
37.69 + final List<Channel> list = Channel.getAll();
37.70 + for (Channel g : list)
37.71 {
37.72 - println(g.getName() + "\t" + "-");
37.73 + conn.println(g.getName() + "\t" + "-");
37.74 }
37.75 - println(".");
37.76 + conn.println(".");
37.77 }
37.78 else if (command[1].equalsIgnoreCase("SUBSCRIPTIONS"))
37.79 {
37.80 - printStatus(215, "information follows");
37.81 - println(".");
37.82 + conn.println("215 information follows");
37.83 + conn.println(".");
37.84 }
37.85 else if (command[1].equalsIgnoreCase("EXTENSIONS"))
37.86 {
37.87 - printStatus(202, "Supported NNTP extensions.");
37.88 - println("LISTGROUP");
37.89 - println(".");
37.90 -
37.91 + conn.println("202 Supported NNTP extensions.");
37.92 + conn.println("LISTGROUP");
37.93 + conn.println("XDAEMON");
37.94 + conn.println("XPAT");
37.95 + conn.println(".");
37.96 }
37.97 else
37.98 {
37.99 - printStatus(500, "unknown argument to LIST command");
37.100 + conn.println("500 unknown argument to LIST command");
37.101 }
37.102 }
37.103 else
37.104 {
37.105 - final List<Group> groups = Group.getAll();
37.106 + final List<Channel> groups = Channel.getAll();
37.107 if(groups != null)
37.108 {
37.109 - printStatus(215, "list of newsgroups follows");
37.110 - for (Group g : groups)
37.111 + conn.println("215 list of newsgroups follows");
37.112 + for (Channel g : groups)
37.113 {
37.114 - // Indeed first the higher article number then the lower
37.115 - println(g.getName() + " " + g.getLastArticleNumber() + " "
37.116 - + g.getFirstArticleNumber() + " y");
37.117 + if(!g.isDeleted())
37.118 + {
37.119 + String writeable = g.isWriteable() ? " y" : " n";
37.120 + // Indeed first the higher article number then the lower
37.121 + conn.println(g.getName() + " " + g.getLastArticleNumber() + " "
37.122 + + g.getFirstArticleNumber() + writeable);
37.123 + }
37.124 }
37.125 - println(".");
37.126 + conn.println(".");
37.127 }
37.128 else
37.129 {
37.130 - printStatus(500, "server database malfunction");
37.131 + conn.println("500 server database malfunction");
37.132 }
37.133 }
37.134 }
38.1 --- a/org/sonews/daemon/command/ListGroupCommand.java Wed Jul 01 10:48:22 2009 +0200
38.2 +++ b/org/sonews/daemon/command/ListGroupCommand.java Wed Jul 22 14:04:05 2009 +0200
38.3 @@ -19,10 +19,10 @@
38.4 package org.sonews.daemon.command;
38.5
38.6 import java.io.IOException;
38.7 -import java.sql.SQLException;
38.8 import java.util.List;
38.9 import org.sonews.daemon.NNTPConnection;
38.10 -import org.sonews.daemon.storage.Group;
38.11 +import org.sonews.storage.Channel;
38.12 +import org.sonews.storage.StorageBackendException;
38.13
38.14 /**
38.15 * Class handling the LISTGROUP command.
38.16 @@ -30,12 +30,13 @@
38.17 * @author Dennis Schwerdel
38.18 * @since n3tpd/0.1
38.19 */
38.20 -public class ListGroupCommand extends AbstractCommand
38.21 +public class ListGroupCommand implements Command
38.22 {
38.23
38.24 - public ListGroupCommand(final NNTPConnection conn)
38.25 + @Override
38.26 + public String[] getSupportedCommandStrings()
38.27 {
38.28 - super(conn);
38.29 + return new String[]{"LISTGROUP"};
38.30 }
38.31
38.32 @Override
38.33 @@ -45,37 +46,43 @@
38.34 }
38.35
38.36 @Override
38.37 - public void processLine(final String commandName)
38.38 - throws IOException, SQLException
38.39 + public boolean isStateful()
38.40 + {
38.41 + return false;
38.42 + }
38.43 +
38.44 + @Override
38.45 + public void processLine(NNTPConnection conn, final String commandName, byte[] raw)
38.46 + throws IOException, StorageBackendException
38.47 {
38.48 final String[] command = commandName.split(" ");
38.49
38.50 - Group group;
38.51 + Channel group;
38.52 if(command.length >= 2)
38.53 {
38.54 - group = Group.getByName(command[1]);
38.55 + group = Channel.getByName(command[1]);
38.56 }
38.57 else
38.58 {
38.59 - group = getCurrentGroup();
38.60 + group = conn.getCurrentChannel();
38.61 }
38.62
38.63 if (group == null)
38.64 {
38.65 - printStatus(412, "no group selected; use GROUP <group> command");
38.66 + conn.println("412 no group selected; use GROUP <group> command");
38.67 return;
38.68 }
38.69
38.70 List<Long> ids = group.getArticleNumbers();
38.71 - printStatus(211, ids.size() + " " +
38.72 + conn.println("211 " + ids.size() + " " +
38.73 group.getFirstArticleNumber() + " " +
38.74 group.getLastArticleNumber() + " list of article numbers follow");
38.75 for(long id : ids)
38.76 {
38.77 // One index number per line
38.78 - println(Long.toString(id));
38.79 + conn.println(Long.toString(id));
38.80 }
38.81 - println(".");
38.82 + conn.println(".");
38.83 }
38.84
38.85 }
39.1 --- a/org/sonews/daemon/command/ModeReaderCommand.java Wed Jul 01 10:48:22 2009 +0200
39.2 +++ b/org/sonews/daemon/command/ModeReaderCommand.java Wed Jul 22 14:04:05 2009 +0200
39.3 @@ -19,8 +19,8 @@
39.4 package org.sonews.daemon.command;
39.5
39.6 import java.io.IOException;
39.7 -import java.sql.SQLException;
39.8 import org.sonews.daemon.NNTPConnection;
39.9 +import org.sonews.storage.StorageBackendException;
39.10
39.11 /**
39.12 * Class handling the MODE READER command. This command actually does nothing
39.13 @@ -28,14 +28,15 @@
39.14 * @author Christian Lins
39.15 * @since sonews/0.5.0
39.16 */
39.17 -public class ModeReaderCommand extends AbstractCommand
39.18 +public class ModeReaderCommand implements Command
39.19 {
39.20 +
39.21 + @Override
39.22 + public String[] getSupportedCommandStrings()
39.23 + {
39.24 + return new String[]{"MODE"};
39.25 + }
39.26
39.27 - public ModeReaderCommand(final NNTPConnection conn)
39.28 - {
39.29 - super(conn);
39.30 - }
39.31 -
39.32 @Override
39.33 public boolean hasFinished()
39.34 {
39.35 @@ -43,15 +44,22 @@
39.36 }
39.37
39.38 @Override
39.39 - public void processLine(final String line) throws IOException, SQLException
39.40 + public boolean isStateful()
39.41 + {
39.42 + return false;
39.43 + }
39.44 +
39.45 + @Override
39.46 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
39.47 + throws IOException, StorageBackendException
39.48 {
39.49 if(line.equalsIgnoreCase("MODE READER"))
39.50 {
39.51 - printStatus(200, "hello you can post");
39.52 + conn.println("200 hello you can post");
39.53 }
39.54 else
39.55 {
39.56 - printStatus(500, "I do not know this mode command");
39.57 + conn.println("500 I do not know this mode command");
39.58 }
39.59 }
39.60
40.1 --- a/org/sonews/daemon/command/NewGroupsCommand.java Wed Jul 01 10:48:22 2009 +0200
40.2 +++ b/org/sonews/daemon/command/NewGroupsCommand.java Wed Jul 22 14:04:05 2009 +0200
40.3 @@ -19,8 +19,8 @@
40.4 package org.sonews.daemon.command;
40.5
40.6 import java.io.IOException;
40.7 -import java.sql.SQLException;
40.8 import org.sonews.daemon.NNTPConnection;
40.9 +import org.sonews.storage.StorageBackendException;
40.10
40.11 /**
40.12 * Class handling the NEWGROUPS command.
40.13 @@ -28,12 +28,13 @@
40.14 * @author Dennis Schwerdel
40.15 * @since n3tpd/0.1
40.16 */
40.17 -public class NewGroupsCommand extends AbstractCommand
40.18 +public class NewGroupsCommand implements Command
40.19 {
40.20
40.21 - public NewGroupsCommand(final NNTPConnection conn)
40.22 + @Override
40.23 + public String[] getSupportedCommandStrings()
40.24 {
40.25 - super(conn);
40.26 + return new String[]{"NEWGROUPS"};
40.27 }
40.28
40.29 @Override
40.30 @@ -43,22 +44,28 @@
40.31 }
40.32
40.33 @Override
40.34 - public void processLine(final String line)
40.35 - throws IOException, SQLException
40.36 + public boolean isStateful()
40.37 + {
40.38 + return false;
40.39 + }
40.40 +
40.41 + @Override
40.42 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
40.43 + throws IOException, StorageBackendException
40.44 {
40.45 final String[] command = line.split(" ");
40.46
40.47 if(command.length == 3)
40.48 {
40.49 - printStatus(231, "list of new newsgroups follows");
40.50 + conn.println("231 list of new newsgroups follows");
40.51
40.52 // Currently we do not store a group's creation date;
40.53 // so we return an empty list which is a valid response
40.54 - println(".");
40.55 + conn.println(".");
40.56 }
40.57 else
40.58 {
40.59 - printStatus(500, "invalid command usage");
40.60 + conn.println("500 invalid command usage");
40.61 }
40.62 }
40.63
41.1 --- a/org/sonews/daemon/command/NextPrevCommand.java Wed Jul 01 10:48:22 2009 +0200
41.2 +++ b/org/sonews/daemon/command/NextPrevCommand.java Wed Jul 22 14:04:05 2009 +0200
41.3 @@ -19,10 +19,10 @@
41.4 package org.sonews.daemon.command;
41.5
41.6 import java.io.IOException;
41.7 -import java.sql.SQLException;
41.8 import org.sonews.daemon.NNTPConnection;
41.9 -import org.sonews.daemon.storage.Article;
41.10 -import org.sonews.daemon.storage.Group;
41.11 +import org.sonews.storage.Article;
41.12 +import org.sonews.storage.Channel;
41.13 +import org.sonews.storage.StorageBackendException;
41.14
41.15 /**
41.16 * Class handling the NEXT and LAST command.
41.17 @@ -30,12 +30,13 @@
41.18 * @author Dennis Schwerdel
41.19 * @since n3tpd/0.1
41.20 */
41.21 -public class NextPrevCommand extends AbstractCommand
41.22 +public class NextPrevCommand implements Command
41.23 {
41.24
41.25 - public NextPrevCommand(final NNTPConnection conn)
41.26 + @Override
41.27 + public String[] getSupportedCommandStrings()
41.28 {
41.29 - super(conn);
41.30 + return new String[]{"NEXT", "PREV"};
41.31 }
41.32
41.33 @Override
41.34 @@ -45,21 +46,27 @@
41.35 }
41.36
41.37 @Override
41.38 - public void processLine(final String line)
41.39 - throws IOException, SQLException
41.40 + public boolean isStateful()
41.41 {
41.42 - final Article currA = getCurrentArticle();
41.43 - final Group currG = getCurrentGroup();
41.44 + return false;
41.45 + }
41.46 +
41.47 + @Override
41.48 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
41.49 + throws IOException, StorageBackendException
41.50 + {
41.51 + final Article currA = conn.getCurrentArticle();
41.52 + final Channel currG = conn.getCurrentChannel();
41.53
41.54 if (currA == null)
41.55 {
41.56 - printStatus(420, "no current article has been selected");
41.57 + conn.println("420 no current article has been selected");
41.58 return;
41.59 }
41.60
41.61 if (currG == null)
41.62 {
41.63 - printStatus(412, "no newsgroup selected");
41.64 + conn.println("412 no newsgroup selected");
41.65 return;
41.66 }
41.67
41.68 @@ -67,33 +74,36 @@
41.69
41.70 if(command[0].equalsIgnoreCase("NEXT"))
41.71 {
41.72 - selectNewArticle(currA, currG, 1);
41.73 + selectNewArticle(conn, currA, currG, 1);
41.74 }
41.75 else if(command[0].equalsIgnoreCase("PREV"))
41.76 {
41.77 - selectNewArticle(currA, currG, -1);
41.78 + selectNewArticle(conn, currA, currG, -1);
41.79 }
41.80 else
41.81 {
41.82 - printStatus(500, "internal server error");
41.83 + conn.println("500 internal server error");
41.84 }
41.85 }
41.86
41.87 - private void selectNewArticle(Article article, Group grp, final int delta)
41.88 - throws IOException, SQLException
41.89 + private void selectNewArticle(NNTPConnection conn, Article article, Channel grp,
41.90 + final int delta)
41.91 + throws IOException, StorageBackendException
41.92 {
41.93 assert article != null;
41.94
41.95 - article = Article.getByArticleNumber(article.getIndexInGroup(grp) + delta, grp);
41.96 + article = grp.getArticle(grp.getIndexOf(article) + delta);
41.97
41.98 if(article == null)
41.99 {
41.100 - printStatus(421, "no next article in this group");
41.101 + conn.println("421 no next article in this group");
41.102 }
41.103 else
41.104 {
41.105 - setCurrentArticle(article);
41.106 - printStatus(223, article.getIndexInGroup(getCurrentGroup()) + " " + article.getMessageID() + " article retrieved - request text separately");
41.107 + conn.setCurrentArticle(article);
41.108 + conn.println("223 " + conn.getCurrentChannel().getIndexOf(article)
41.109 + + " " + article.getMessageID()
41.110 + + " article retrieved - request text separately");
41.111 }
41.112 }
41.113
42.1 --- a/org/sonews/daemon/command/OverCommand.java Wed Jul 01 10:48:22 2009 +0200
42.2 +++ b/org/sonews/daemon/command/OverCommand.java Wed Jul 22 14:04:05 2009 +0200
42.3 @@ -19,13 +19,13 @@
42.4 package org.sonews.daemon.command;
42.5
42.6 import java.io.IOException;
42.7 -import java.sql.SQLException;
42.8 import java.util.List;
42.9 import org.sonews.util.Log;
42.10 import org.sonews.daemon.NNTPConnection;
42.11 -import org.sonews.daemon.storage.Article;
42.12 -import org.sonews.daemon.storage.ArticleHead;
42.13 -import org.sonews.daemon.storage.Headers;
42.14 +import org.sonews.storage.Article;
42.15 +import org.sonews.storage.ArticleHead;
42.16 +import org.sonews.storage.Headers;
42.17 +import org.sonews.storage.StorageBackendException;
42.18 import org.sonews.util.Pair;
42.19
42.20 /**
42.21 @@ -106,14 +106,15 @@
42.22 * @author Christian Lins
42.23 * @since sonews/0.5.0
42.24 */
42.25 -public class OverCommand extends AbstractCommand
42.26 +public class OverCommand implements Command
42.27 {
42.28
42.29 - public static final int MAX_LINES_PER_DBREQUEST = 100;
42.30 -
42.31 - public OverCommand(final NNTPConnection conn)
42.32 + public static final int MAX_LINES_PER_DBREQUEST = 200;
42.33 +
42.34 + @Override
42.35 + public String[] getSupportedCommandStrings()
42.36 {
42.37 - super(conn);
42.38 + return new String[]{"OVER", "XOVER"};
42.39 }
42.40
42.41 @Override
42.42 @@ -123,12 +124,18 @@
42.43 }
42.44
42.45 @Override
42.46 - public void processLine(final String line)
42.47 - throws IOException, SQLException
42.48 + public boolean isStateful()
42.49 {
42.50 - if(getCurrentGroup() == null)
42.51 + return false;
42.52 + }
42.53 +
42.54 + @Override
42.55 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
42.56 + throws IOException, StorageBackendException
42.57 + {
42.58 + if(conn.getCurrentChannel() == null)
42.59 {
42.60 - printStatus(412, "No news group current selected");
42.61 + conn.println("412 no newsgroup selected");
42.62 }
42.63 else
42.64 {
42.65 @@ -138,20 +145,20 @@
42.66 // the currently selected article(s)
42.67 if(command.length == 1)
42.68 {
42.69 - final Article art = getCurrentArticle();
42.70 + final Article art = conn.getCurrentArticle();
42.71 if(art == null)
42.72 {
42.73 - printStatus(420, "No article(s) selected");
42.74 + conn.println("420 no article(s) selected");
42.75 return;
42.76 }
42.77
42.78 - println(buildOverview(art, -1));
42.79 + conn.println(buildOverview(art, -1));
42.80 }
42.81 // otherwise print information about the specified range
42.82 else
42.83 {
42.84 - int artStart;
42.85 - int artEnd = getCurrentGroup().getLastArticleNumber();
42.86 + long artStart;
42.87 + long artEnd = conn.getCurrentChannel().getLastArticleNumber();
42.88 String[] nums = command[1].split("-");
42.89 if(nums.length >= 1)
42.90 {
42.91 @@ -167,7 +174,7 @@
42.92 }
42.93 else
42.94 {
42.95 - artStart = getCurrentGroup().getFirstArticleNumber();
42.96 + artStart = conn.getCurrentChannel().getFirstArticleNumber();
42.97 }
42.98
42.99 if(nums.length >=2)
42.100 @@ -186,41 +193,41 @@
42.101 {
42.102 if(command[0].equalsIgnoreCase("OVER"))
42.103 {
42.104 - printStatus(423, "No articles in that range");
42.105 + conn.println("423 no articles in that range");
42.106 }
42.107 else
42.108 {
42.109 - printStatus(224, "(empty) overview information follows:");
42.110 - println(".");
42.111 + conn.println("224 (empty) overview information follows:");
42.112 + conn.println(".");
42.113 }
42.114 }
42.115 else
42.116 {
42.117 - for(int n = artStart; n <= artEnd; n += MAX_LINES_PER_DBREQUEST)
42.118 + for(long n = artStart; n <= artEnd; n += MAX_LINES_PER_DBREQUEST)
42.119 {
42.120 - int nEnd = Math.min(n + MAX_LINES_PER_DBREQUEST - 1, artEnd);
42.121 - List<Pair<Long, ArticleHead>> articleHeads = getCurrentGroup()
42.122 + long nEnd = Math.min(n + MAX_LINES_PER_DBREQUEST - 1, artEnd);
42.123 + List<Pair<Long, ArticleHead>> articleHeads = conn.getCurrentChannel()
42.124 .getArticleHeads(n, nEnd);
42.125 if(articleHeads.isEmpty() && n == artStart
42.126 && command[0].equalsIgnoreCase("OVER"))
42.127 {
42.128 // This reply is only valid for OVER, not for XOVER command
42.129 - printStatus(423, "No articles in that range");
42.130 + conn.println("423 no articles in that range");
42.131 return;
42.132 }
42.133 else if(n == artStart)
42.134 {
42.135 // XOVER replies this although there is no data available
42.136 - printStatus(224, "Overview information follows");
42.137 + conn.println("224 overview information follows");
42.138 }
42.139
42.140 for(Pair<Long, ArticleHead> article : articleHeads)
42.141 {
42.142 String overview = buildOverview(article.getB(), article.getA());
42.143 - println(overview);
42.144 + conn.println(overview);
42.145 }
42.146 } // for
42.147 - println(".");
42.148 + conn.println(".");
42.149 }
42.150 }
42.151 }
43.1 --- a/org/sonews/daemon/command/PostCommand.java Wed Jul 01 10:48:22 2009 +0200
43.2 +++ b/org/sonews/daemon/command/PostCommand.java Wed Jul 22 14:04:05 2009 +0200
43.3 @@ -19,24 +19,26 @@
43.4 package org.sonews.daemon.command;
43.5
43.6 import java.io.IOException;
43.7 -
43.8 import java.io.ByteArrayInputStream;
43.9 +import java.io.ByteArrayOutputStream;
43.10 import java.nio.charset.Charset;
43.11 import java.nio.charset.IllegalCharsetNameException;
43.12 import java.nio.charset.UnsupportedCharsetException;
43.13 import java.sql.SQLException;
43.14 +import java.util.Arrays;
43.15 import java.util.Locale;
43.16 import javax.mail.MessagingException;
43.17 import javax.mail.internet.AddressException;
43.18 import javax.mail.internet.InternetHeaders;
43.19 -import org.sonews.daemon.Config;
43.20 +import org.sonews.config.Config;
43.21 import org.sonews.util.Log;
43.22 import org.sonews.mlgw.Dispatcher;
43.23 -import org.sonews.daemon.storage.Article;
43.24 -import org.sonews.daemon.storage.Database;
43.25 -import org.sonews.daemon.storage.Group;
43.26 +import org.sonews.storage.Article;
43.27 +import org.sonews.storage.Group;
43.28 import org.sonews.daemon.NNTPConnection;
43.29 -import org.sonews.daemon.storage.Headers;
43.30 +import org.sonews.storage.Headers;
43.31 +import org.sonews.storage.StorageBackendException;
43.32 +import org.sonews.storage.StorageManager;
43.33 import org.sonews.feed.FeedManager;
43.34 import org.sonews.util.Stats;
43.35
43.36 @@ -47,7 +49,7 @@
43.37 * @author Christian Lins
43.38 * @since sonews/0.5.0
43.39 */
43.40 -public class PostCommand extends AbstractCommand
43.41 +public class PostCommand implements Command
43.42 {
43.43
43.44 private final Article article = new Article();
43.45 @@ -55,14 +57,15 @@
43.46 private long bodySize = 0;
43.47 private InternetHeaders headers = null;
43.48 private long maxBodySize =
43.49 - Config.getInstance().get(Config.ARTICLE_MAXSIZE, 128) * 1024L; // Size in bytes
43.50 + Config.inst().get(Config.ARTICLE_MAXSIZE, 128) * 1024L; // Size in bytes
43.51 private PostState state = PostState.WaitForLineOne;
43.52 - private final StringBuilder strBody = new StringBuilder();
43.53 - private final StringBuilder strHead = new StringBuilder();
43.54 -
43.55 - public PostCommand(final NNTPConnection conn)
43.56 + private final ByteArrayOutputStream bufBody = new ByteArrayOutputStream();
43.57 + private final StringBuilder strHead = new StringBuilder();
43.58 +
43.59 + @Override
43.60 + public String[] getSupportedCommandStrings()
43.61 {
43.62 - super(conn);
43.63 + return new String[]{"POST"};
43.64 }
43.65
43.66 @Override
43.67 @@ -71,6 +74,12 @@
43.68 return this.state == PostState.Finished;
43.69 }
43.70
43.71 + @Override
43.72 + public boolean isStateful()
43.73 + {
43.74 + return true;
43.75 + }
43.76 +
43.77 /**
43.78 * Process the given line String. line.trim() was called by NNTPConnection.
43.79 * @param line
43.80 @@ -78,8 +87,8 @@
43.81 * @throws java.sql.SQLException
43.82 */
43.83 @Override // TODO: Refactor this method to reduce complexity!
43.84 - public void processLine(String line)
43.85 - throws IOException, SQLException
43.86 + public void processLine(NNTPConnection conn, String line, byte[] raw)
43.87 + throws IOException, StorageBackendException
43.88 {
43.89 switch(state)
43.90 {
43.91 @@ -87,12 +96,12 @@
43.92 {
43.93 if(line.equalsIgnoreCase("POST"))
43.94 {
43.95 - printStatus(340, "send article to be posted. End with <CR-LF>.<CR-LF>");
43.96 + conn.println("340 send article to be posted. End with <CR-LF>.<CR-LF>");
43.97 state = PostState.ReadingHeaders;
43.98 }
43.99 else
43.100 {
43.101 - printStatus(500, "invalid command usage");
43.102 + conn.println("500 invalid command usage");
43.103 }
43.104 break;
43.105 }
43.106 @@ -111,7 +120,7 @@
43.107 // Parse the header using the InternetHeader class from JavaMail API
43.108 headers = new InternetHeaders(
43.109 new ByteArrayInputStream(strHead.toString().trim()
43.110 - .getBytes(connection.getCurrentCharset())));
43.111 + .getBytes(conn.getCurrentCharset())));
43.112
43.113 // add the header entries for the article
43.114 article.setHeaders(headers);
43.115 @@ -119,21 +128,21 @@
43.116 catch (MessagingException e)
43.117 {
43.118 e.printStackTrace();
43.119 - printStatus(500, "posting failed - invalid header");
43.120 + conn.println("500 posting failed - invalid header");
43.121 state = PostState.Finished;
43.122 break;
43.123 }
43.124
43.125 // Change charset for reading body;
43.126 // for multipart messages UTF-8 is returned
43.127 - connection.setCurrentCharset(article.getBodyCharset());
43.128 + //conn.setCurrentCharset(article.getBodyCharset());
43.129
43.130 state = PostState.ReadingBody;
43.131
43.132 if(".".equals(line))
43.133 {
43.134 // Post an article without body
43.135 - postArticle(article);
43.136 + postArticle(conn, article);
43.137 state = PostState.Finished;
43.138 }
43.139 }
43.140 @@ -146,15 +155,16 @@
43.141 // Set some headers needed for Over command
43.142 headers.setHeader(Headers.LINES, Integer.toString(lineCount));
43.143 headers.setHeader(Headers.BYTES, Long.toString(bodySize));
43.144 +
43.145 + byte[] body = bufBody.toByteArray();
43.146 + if(body.length >= 2)
43.147 + {
43.148 + // Remove trailing CRLF
43.149 + body = Arrays.copyOf(body, body.length - 2);
43.150 + }
43.151 + article.setBody(body); // set the article body
43.152
43.153 - if(strBody.length() >= 2)
43.154 - {
43.155 - strBody.deleteCharAt(strBody.length() - 1); // Remove last newline
43.156 - strBody.deleteCharAt(strBody.length() - 1); // Remove last CR
43.157 - }
43.158 - article.setBody(strBody.toString()); // set the article body
43.159 -
43.160 - postArticle(article);
43.161 + postArticle(conn, article);
43.162 state = PostState.Finished;
43.163 }
43.164 else
43.165 @@ -163,19 +173,19 @@
43.166 lineCount++;
43.167
43.168 // Add line to body buffer
43.169 - strBody.append(line);
43.170 - strBody.append(NNTPConnection.NEWLINE);
43.171 + bufBody.write(raw, 0, raw.length);
43.172 + bufBody.write(NNTPConnection.NEWLINE.getBytes());
43.173
43.174 if(bodySize > maxBodySize)
43.175 {
43.176 - printStatus(500, "article is too long");
43.177 + conn.println("500 article is too long");
43.178 state = PostState.Finished;
43.179 break;
43.180 }
43.181
43.182 // Check if this message is a MIME-multipart message and needs a
43.183 // charset change
43.184 - try
43.185 + /*try
43.186 {
43.187 line = line.toLowerCase(Locale.ENGLISH);
43.188 if(line.startsWith(Headers.CONTENT_TYPE))
43.189 @@ -197,7 +207,7 @@
43.190
43.191 try
43.192 {
43.193 - connection.setCurrentCharset(Charset.forName(charsetName));
43.194 + conn.setCurrentCharset(Charset.forName(charsetName));
43.195 }
43.196 catch(IllegalCharsetNameException ex)
43.197 {
43.198 @@ -213,12 +223,15 @@
43.199 catch(Exception ex)
43.200 {
43.201 ex.printStackTrace();
43.202 - }
43.203 + }*/
43.204 }
43.205 break;
43.206 }
43.207 default:
43.208 + {
43.209 + // Should never happen
43.210 Log.msg("PostCommand::processLine(): already finished...", false);
43.211 + }
43.212 }
43.213 }
43.214
43.215 @@ -226,7 +239,7 @@
43.216 * Article is a control message and needs special handling.
43.217 * @param article
43.218 */
43.219 - private void controlMessage(Article article)
43.220 + private void controlMessage(NNTPConnection conn, Article article)
43.221 throws IOException
43.222 {
43.223 String[] ctrl = article.getHeader(Headers.CONTROL)[0].split(" ");
43.224 @@ -234,52 +247,52 @@
43.225 {
43.226 try
43.227 {
43.228 - Database.getInstance().delete(ctrl[1]);
43.229 + StorageManager.current().delete(ctrl[1]);
43.230
43.231 // Move cancel message to "control" group
43.232 article.setHeader(Headers.NEWSGROUPS, "control");
43.233 - Database.getInstance().addArticle(article);
43.234 - printStatus(240, "article cancelled");
43.235 + StorageManager.current().addArticle(article);
43.236 + conn.println("240 article cancelled");
43.237 }
43.238 - catch(SQLException ex)
43.239 + catch(StorageBackendException ex)
43.240 {
43.241 Log.msg(ex, false);
43.242 - printStatus(500, "internal server error");
43.243 + conn.println("500 internal server error");
43.244 }
43.245 }
43.246 else
43.247 {
43.248 - printStatus(441, "unknown Control header");
43.249 + conn.println("441 unknown control header");
43.250 }
43.251 }
43.252
43.253 - private void supersedeMessage(Article article)
43.254 + private void supersedeMessage(NNTPConnection conn, Article article)
43.255 throws IOException
43.256 {
43.257 try
43.258 {
43.259 String oldMsg = article.getHeader(Headers.SUPERSEDES)[0];
43.260 - Database.getInstance().delete(oldMsg);
43.261 - Database.getInstance().addArticle(article);
43.262 - printStatus(240, "article replaced");
43.263 + StorageManager.current().delete(oldMsg);
43.264 + StorageManager.current().addArticle(article);
43.265 + conn.println("240 article replaced");
43.266 }
43.267 - catch(SQLException ex)
43.268 + catch(StorageBackendException ex)
43.269 {
43.270 Log.msg(ex, false);
43.271 - printStatus(500, "internal server error");
43.272 + conn.println("500 internal server error");
43.273 }
43.274 }
43.275
43.276 - private void postArticle(Article article)
43.277 + private void postArticle(NNTPConnection conn, Article article)
43.278 throws IOException
43.279 {
43.280 if(article.getHeader(Headers.CONTROL)[0].length() > 0)
43.281 {
43.282 - controlMessage(article);
43.283 + controlMessage(conn, article);
43.284 }
43.285 else if(article.getHeader(Headers.SUPERSEDES)[0].length() > 0)
43.286 {
43.287 - supersedeMessage(article);
43.288 + supersedeMessage(conn, article);
43.289 }
43.290 else // Post the article regularily
43.291 {
43.292 @@ -291,10 +304,10 @@
43.293 String[] groupnames = article.getHeader(Headers.NEWSGROUPS)[0].split(",");
43.294 for(String groupname : groupnames)
43.295 {
43.296 - Group group = Database.getInstance().getGroup(groupname);
43.297 - if(group != null)
43.298 + Group group = StorageManager.current().getGroup(groupname);
43.299 + if(group != null && !group.isDeleted())
43.300 {
43.301 - if(group.isMailingList() && !connection.isLocalConnection())
43.302 + if(group.isMailingList() && !conn.isLocalConnection())
43.303 {
43.304 // Send to mailing list; the Dispatcher writes
43.305 // statistics to database
43.306 @@ -304,9 +317,9 @@
43.307 else
43.308 {
43.309 // Store in database
43.310 - if(!Database.getInstance().isArticleExisting(article.getMessageID()))
43.311 + if(!StorageManager.current().isArticleExisting(article.getMessageID()))
43.312 {
43.313 - Database.getInstance().addArticle(article);
43.314 + StorageManager.current().addArticle(article);
43.315
43.316 // Log this posting to statistics
43.317 Stats.getInstance().mailPosted(
43.318 @@ -319,30 +332,30 @@
43.319
43.320 if(success)
43.321 {
43.322 - printStatus(240, "article posted ok");
43.323 + conn.println("240 article posted ok");
43.324 FeedManager.queueForPush(article);
43.325 }
43.326 else
43.327 {
43.328 - printStatus(441, "newsgroup not found");
43.329 + conn.println("441 newsgroup not found");
43.330 }
43.331 }
43.332 catch(AddressException ex)
43.333 {
43.334 Log.msg(ex.getMessage(), true);
43.335 - printStatus(441, "invalid sender address");
43.336 + conn.println("441 invalid sender address");
43.337 }
43.338 catch(MessagingException ex)
43.339 {
43.340 // A MessageException is thrown when the sender email address is
43.341 // invalid or something is wrong with the SMTP server.
43.342 System.err.println(ex.getLocalizedMessage());
43.343 - printStatus(441, ex.getClass().getCanonicalName() + ": " + ex.getLocalizedMessage());
43.344 + conn.println("441 " + ex.getClass().getCanonicalName() + ": " + ex.getLocalizedMessage());
43.345 }
43.346 - catch(SQLException ex)
43.347 + catch(StorageBackendException ex)
43.348 {
43.349 ex.printStackTrace();
43.350 - printStatus(500, "internal server error");
43.351 + conn.println("500 internal server error");
43.352 }
43.353 }
43.354 }
44.1 --- a/org/sonews/daemon/command/QuitCommand.java Wed Jul 01 10:48:22 2009 +0200
44.2 +++ b/org/sonews/daemon/command/QuitCommand.java Wed Jul 22 14:04:05 2009 +0200
44.3 @@ -19,20 +19,21 @@
44.4 package org.sonews.daemon.command;
44.5
44.6 import java.io.IOException;
44.7 -import java.sql.SQLException;
44.8 import org.sonews.daemon.NNTPConnection;
44.9 +import org.sonews.storage.StorageBackendException;
44.10
44.11 /**
44.12 * Implementation of the QUIT command; client wants to shutdown the connection.
44.13 * @author Christian Lins
44.14 * @since sonews/0.5.0
44.15 */
44.16 -public class QuitCommand extends AbstractCommand
44.17 +public class QuitCommand implements Command
44.18 {
44.19
44.20 - public QuitCommand(final NNTPConnection conn)
44.21 + @Override
44.22 + public String[] getSupportedCommandStrings()
44.23 {
44.24 - super(conn);
44.25 + return new String[]{"QUIT"};
44.26 }
44.27
44.28 @Override
44.29 @@ -42,13 +43,19 @@
44.30 }
44.31
44.32 @Override
44.33 - public void processLine(final String line)
44.34 - throws IOException, SQLException
44.35 + public boolean isStateful()
44.36 + {
44.37 + return false;
44.38 + }
44.39 +
44.40 + @Override
44.41 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
44.42 + throws IOException, StorageBackendException
44.43 {
44.44 - printStatus(205, "cya");
44.45 + conn.println("205 cya");
44.46
44.47 - this.connection.shutdownInput();
44.48 - this.connection.shutdownOutput();
44.49 + conn.shutdownInput();
44.50 + conn.shutdownOutput();
44.51 }
44.52
44.53 }
45.1 --- a/org/sonews/daemon/command/StatCommand.java Wed Jul 01 10:48:22 2009 +0200
45.2 +++ b/org/sonews/daemon/command/StatCommand.java Wed Jul 22 14:04:05 2009 +0200
45.3 @@ -19,21 +19,22 @@
45.4 package org.sonews.daemon.command;
45.5
45.6 import java.io.IOException;
45.7 -import java.sql.SQLException;
45.8 -import org.sonews.daemon.storage.Article;
45.9 +import org.sonews.storage.Article;
45.10 import org.sonews.daemon.NNTPConnection;
45.11 +import org.sonews.storage.StorageBackendException;
45.12
45.13 /**
45.14 * Implementation of the STAT command.
45.15 * @author Christian Lins
45.16 * @since sonews/0.5.0
45.17 */
45.18 -public class StatCommand extends AbstractCommand
45.19 +public class StatCommand implements Command
45.20 {
45.21
45.22 - public StatCommand(final NNTPConnection conn)
45.23 + @Override
45.24 + public String[] getSupportedCommandStrings()
45.25 {
45.26 - super(conn);
45.27 + return new String[]{"STAT"};
45.28 }
45.29
45.30 @Override
45.31 @@ -42,20 +43,26 @@
45.32 return true;
45.33 }
45.34
45.35 + @Override
45.36 + public boolean isStateful()
45.37 + {
45.38 + return false;
45.39 + }
45.40 +
45.41 // TODO: Method has various exit points => Refactor!
45.42 @Override
45.43 - public void processLine(final String line)
45.44 - throws IOException, SQLException
45.45 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
45.46 + throws IOException, StorageBackendException
45.47 {
45.48 final String[] command = line.split(" ");
45.49
45.50 Article article = null;
45.51 if(command.length == 1)
45.52 {
45.53 - article = getCurrentArticle();
45.54 + article = conn.getCurrentArticle();
45.55 if(article == null)
45.56 {
45.57 - printStatus(420, "no current article has been selected");
45.58 + conn.println("420 no current article has been selected");
45.59 return;
45.60 }
45.61 }
45.62 @@ -65,7 +72,7 @@
45.63 article = Article.getByMessageID(command[1]);
45.64 if (article == null)
45.65 {
45.66 - printStatus(430, "no such article found");
45.67 + conn.println("430 no such article found");
45.68 return;
45.69 }
45.70 }
45.71 @@ -75,26 +82,27 @@
45.72 try
45.73 {
45.74 long aid = Long.parseLong(command[1]);
45.75 - article = Article.getByArticleNumber(aid, getCurrentGroup());
45.76 + article = conn.getCurrentChannel().getArticle(aid);
45.77 }
45.78 catch(NumberFormatException ex)
45.79 {
45.80 ex.printStackTrace();
45.81 }
45.82 - catch(SQLException ex)
45.83 + catch(StorageBackendException ex)
45.84 {
45.85 ex.printStackTrace();
45.86 }
45.87 if (article == null)
45.88 {
45.89 - printStatus(423, "no such article number in this group");
45.90 + conn.println("423 no such article number in this group");
45.91 return;
45.92 }
45.93 - setCurrentArticle(article);
45.94 + conn.setCurrentArticle(article);
45.95 }
45.96
45.97 - printStatus(223, article.getIndexInGroup(getCurrentGroup()) + " " + article.getMessageID()
45.98 - + " article retrieved - request text separately");
45.99 + conn.println("223 " + conn.getCurrentChannel().getIndexOf(article) + " "
45.100 + + article.getMessageID()
45.101 + + " article retrieved - request text separately");
45.102 }
45.103
45.104 }
46.1 --- a/org/sonews/daemon/command/UnsupportedCommand.java Wed Jul 01 10:48:22 2009 +0200
46.2 +++ b/org/sonews/daemon/command/UnsupportedCommand.java Wed Jul 22 14:04:05 2009 +0200
46.3 @@ -27,14 +27,18 @@
46.4 * @author Christian Lins
46.5 * @since sonews/0.5.0
46.6 */
46.7 -public class UnsupportedCommand extends AbstractCommand
46.8 +public class UnsupportedCommand implements Command
46.9 {
46.10 +
46.11 + /**
46.12 + * @return Always returns null.
46.13 + */
46.14 + @Override
46.15 + public String[] getSupportedCommandStrings()
46.16 + {
46.17 + return null;
46.18 + }
46.19
46.20 - public UnsupportedCommand(final NNTPConnection conn)
46.21 - {
46.22 - super(conn);
46.23 - }
46.24 -
46.25 @Override
46.26 public boolean hasFinished()
46.27 {
46.28 @@ -42,10 +46,16 @@
46.29 }
46.30
46.31 @Override
46.32 - public void processLine(final String line)
46.33 + public boolean isStateful()
46.34 + {
46.35 + return false;
46.36 + }
46.37 +
46.38 + @Override
46.39 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
46.40 throws IOException
46.41 {
46.42 - printStatus(500, "command not supported");
46.43 + conn.println("500 command not supported");
46.44 }
46.45
46.46 }
47.1 --- a/org/sonews/daemon/command/XDaemonCommand.java Wed Jul 01 10:48:22 2009 +0200
47.2 +++ b/org/sonews/daemon/command/XDaemonCommand.java Wed Jul 22 14:04:05 2009 +0200
47.3 @@ -20,15 +20,14 @@
47.4
47.5 import java.io.IOException;
47.6 import java.net.InetSocketAddress;
47.7 -import java.sql.SQLException;
47.8 import java.util.List;
47.9 -import org.sonews.daemon.BootstrapConfig;
47.10 -import org.sonews.daemon.Config;
47.11 +import org.sonews.config.Config;
47.12 import org.sonews.daemon.NNTPConnection;
47.13 -import org.sonews.daemon.storage.Database;
47.14 -import org.sonews.daemon.storage.Group;
47.15 +import org.sonews.storage.StorageBackendException;
47.16 +import org.sonews.storage.StorageManager;
47.17 import org.sonews.feed.FeedManager;
47.18 import org.sonews.feed.Subscription;
47.19 +import org.sonews.storage.Group;
47.20 import org.sonews.util.Stats;
47.21
47.22 /**
47.23 @@ -40,12 +39,13 @@
47.24 * @author Christian Lins
47.25 * @since sonews/0.5.0
47.26 */
47.27 -public class XDaemonCommand extends AbstractCommand
47.28 +public class XDaemonCommand implements Command
47.29 {
47.30 -
47.31 - public XDaemonCommand(NNTPConnection conn)
47.32 +
47.33 + @Override
47.34 + public String[] getSupportedCommandStrings()
47.35 {
47.36 - super(conn);
47.37 + return new String[]{"XDAEMON"};
47.38 }
47.39
47.40 @Override
47.41 @@ -54,94 +54,102 @@
47.42 return true;
47.43 }
47.44
47.45 + @Override
47.46 + public boolean isStateful()
47.47 + {
47.48 + return false;
47.49 + }
47.50 +
47.51 // TODO: Refactor this method to reduce complexity!
47.52 @Override
47.53 - public void processLine(String line) throws IOException, SQLException
47.54 + public void processLine(NNTPConnection conn, String line, byte[] raw)
47.55 + throws IOException, StorageBackendException
47.56 {
47.57 - InetSocketAddress addr = (InetSocketAddress)connection.getChannel().socket()
47.58 + InetSocketAddress addr = (InetSocketAddress)conn.getSocketChannel().socket()
47.59 .getRemoteSocketAddress();
47.60 if(addr.getHostName().equals(
47.61 - BootstrapConfig.getInstance().get(BootstrapConfig.XDAEMON_HOST, "localhost")))
47.62 + Config.inst().get(Config.XDAEMON_HOST, "localhost")))
47.63 {
47.64 String[] commands = line.split(" ", 4);
47.65 if(commands.length == 3 && commands[1].equalsIgnoreCase("LIST"))
47.66 {
47.67 if(commands[2].equalsIgnoreCase("CONFIGKEYS"))
47.68 {
47.69 - printStatus(200, "list of available config keys follows");
47.70 + conn.println("100 list of available config keys follows");
47.71 for(String key : Config.AVAILABLE_KEYS)
47.72 {
47.73 - println(key);
47.74 + conn.println(key);
47.75 }
47.76 - println(".");
47.77 + conn.println(".");
47.78 }
47.79 else if(commands[2].equalsIgnoreCase("PEERINGRULES"))
47.80 {
47.81 List<Subscription> pull =
47.82 - Database.getInstance().getSubscriptions(FeedManager.TYPE_PULL);
47.83 + StorageManager.current().getSubscriptions(FeedManager.TYPE_PULL);
47.84 List<Subscription> push =
47.85 - Database.getInstance().getSubscriptions(FeedManager.TYPE_PUSH);
47.86 - printStatus(200,"list of peering rules follows");
47.87 + StorageManager.current().getSubscriptions(FeedManager.TYPE_PUSH);
47.88 + conn.println("100 list of peering rules follows");
47.89 for(Subscription sub : pull)
47.90 {
47.91 - println("PULL " + sub.getHost() + ":" + sub.getPort()
47.92 + conn.println("PULL " + sub.getHost() + ":" + sub.getPort()
47.93 + " " + sub.getGroup());
47.94 }
47.95 for(Subscription sub : push)
47.96 {
47.97 - println("PUSH " + sub.getHost() + ":" + sub.getPort()
47.98 + conn.println("PUSH " + sub.getHost() + ":" + sub.getPort()
47.99 + " " + sub.getGroup());
47.100 }
47.101 - println(".");
47.102 + conn.println(".");
47.103 }
47.104 else
47.105 {
47.106 - printStatus(501, "unknown sub command");
47.107 + conn.println("401 unknown sub command");
47.108 }
47.109 }
47.110 else if(commands.length == 3 && commands[1].equalsIgnoreCase("DELETE"))
47.111 {
47.112 - Database.getInstance().delete(commands[2]);
47.113 - printStatus(200, "article " + commands[2] + " deleted");
47.114 + StorageManager.current().delete(commands[2]);
47.115 + conn.println("200 article " + commands[2] + " deleted");
47.116 }
47.117 else if(commands.length == 4 && commands[1].equalsIgnoreCase("GROUPADD"))
47.118 {
47.119 - Database.getInstance().addGroup(commands[2], Integer.parseInt(commands[3]));
47.120 - printStatus(200, "group " + commands[2] + " created");
47.121 + StorageManager.current().addGroup(commands[2], Integer.parseInt(commands[3]));
47.122 + conn.println("200 group " + commands[2] + " created");
47.123 }
47.124 else if(commands.length == 3 && commands[1].equalsIgnoreCase("GROUPDEL"))
47.125 {
47.126 - Group group = Database.getInstance().getGroup(commands[2]);
47.127 + Group group = StorageManager.current().getGroup(commands[2]);
47.128 if(group == null)
47.129 {
47.130 - printStatus(400, "group not found");
47.131 + conn.println("400 group not found");
47.132 }
47.133 else
47.134 {
47.135 group.setFlag(Group.DELETED);
47.136 - printStatus(200, "group " + commands[2] + " marked as deleted");
47.137 + group.update();
47.138 + conn.println("200 group " + commands[2] + " marked as deleted");
47.139 }
47.140 }
47.141 else if(commands.length == 4 && commands[1].equalsIgnoreCase("SET"))
47.142 {
47.143 String key = commands[2];
47.144 String val = commands[3];
47.145 - Config.getInstance().set(key, val);
47.146 - printStatus(200, "new config value set");
47.147 + Config.inst().set(key, val);
47.148 + conn.println("200 new config value set");
47.149 }
47.150 else if(commands.length == 3 && commands[1].equalsIgnoreCase("GET"))
47.151 {
47.152 String key = commands[2];
47.153 - String val = Config.getInstance().get(key, null);
47.154 + String val = Config.inst().get(key, null);
47.155 if(val != null)
47.156 {
47.157 - printStatus(200, "config value for " + key + " follows");
47.158 - println(val);
47.159 - println(".");
47.160 + conn.println("100 config value for " + key + " follows");
47.161 + conn.println(val);
47.162 + conn.println(".");
47.163 }
47.164 else
47.165 {
47.166 - printStatus(400, "config value not set");
47.167 + conn.println("400 config value not set");
47.168 }
47.169 }
47.170 else if(commands.length >= 3 && commands[1].equalsIgnoreCase("LOG"))
47.171 @@ -154,83 +162,83 @@
47.172
47.173 if(commands[2].equalsIgnoreCase("CONNECTED_CLIENTS"))
47.174 {
47.175 - printStatus(200, "number of connections follow");
47.176 - println(Integer.toString(Stats.getInstance().connectedClients()));
47.177 - println(".");
47.178 + conn.println("100 number of connections follow");
47.179 + conn.println(Integer.toString(Stats.getInstance().connectedClients()));
47.180 + conn.println(".");
47.181 }
47.182 else if(commands[2].equalsIgnoreCase("POSTED_NEWS"))
47.183 {
47.184 - printStatus(200, "hourly numbers of posted news yesterday");
47.185 + conn.println("100 hourly numbers of posted news yesterday");
47.186 for(int n = 0; n < 24; n++)
47.187 {
47.188 - println(n + " " + Stats.getInstance()
47.189 + conn.println(n + " " + Stats.getInstance()
47.190 .getYesterdaysEvents(Stats.POSTED_NEWS, n, group));
47.191 }
47.192 - println(".");
47.193 + conn.println(".");
47.194 }
47.195 else if(commands[2].equalsIgnoreCase("GATEWAYED_NEWS"))
47.196 {
47.197 - printStatus(200, "hourly numbers of gatewayed news yesterday");
47.198 + conn.println("100 hourly numbers of gatewayed news yesterday");
47.199 for(int n = 0; n < 24; n++)
47.200 {
47.201 - println(n + " " + Stats.getInstance()
47.202 + conn.println(n + " " + Stats.getInstance()
47.203 .getYesterdaysEvents(Stats.GATEWAYED_NEWS, n, group));
47.204 }
47.205 - println(".");
47.206 + conn.println(".");
47.207 }
47.208 else if(commands[2].equalsIgnoreCase("TRANSMITTED_NEWS"))
47.209 {
47.210 - printStatus(200, "hourly numbers of news transmitted to peers yesterday");
47.211 + conn.println("100 hourly numbers of news transmitted to peers yesterday");
47.212 for(int n = 0; n < 24; n++)
47.213 {
47.214 - println(n + " " + Stats.getInstance()
47.215 + conn.println(n + " " + Stats.getInstance()
47.216 .getYesterdaysEvents(Stats.FEEDED_NEWS, n, group));
47.217 }
47.218 - println(".");
47.219 + conn.println(".");
47.220 }
47.221 else if(commands[2].equalsIgnoreCase("HOSTED_NEWS"))
47.222 {
47.223 - printStatus(200, "number of overall hosted news");
47.224 - println(Integer.toString(Stats.getInstance().getNumberOfNews()));
47.225 - println(".");
47.226 + conn.println("100 number of overall hosted news");
47.227 + conn.println(Integer.toString(Stats.getInstance().getNumberOfNews()));
47.228 + conn.println(".");
47.229 }
47.230 else if(commands[2].equalsIgnoreCase("HOSTED_GROUPS"))
47.231 {
47.232 - printStatus(200, "number of hosted groups");
47.233 - println(Integer.toString(Stats.getInstance().getNumberOfGroups()));
47.234 - println(".");
47.235 + conn.println("100 number of hosted groups");
47.236 + conn.println(Integer.toString(Stats.getInstance().getNumberOfGroups()));
47.237 + conn.println(".");
47.238 }
47.239 else if(commands[2].equalsIgnoreCase("POSTED_NEWS_PER_HOUR"))
47.240 {
47.241 - printStatus(200, "posted news per hour");
47.242 - println(Double.toString(Stats.getInstance().postedPerHour(-1)));
47.243 - println(".");
47.244 + conn.println("100 posted news per hour");
47.245 + conn.println(Double.toString(Stats.getInstance().postedPerHour(-1)));
47.246 + conn.println(".");
47.247 }
47.248 else if(commands[2].equalsIgnoreCase("FEEDED_NEWS_PER_HOUR"))
47.249 {
47.250 - printStatus(200, "feeded news per hour");
47.251 - println(Double.toString(Stats.getInstance().feededPerHour(-1)));
47.252 - println(".");
47.253 + conn.println("100 feeded news per hour");
47.254 + conn.println(Double.toString(Stats.getInstance().feededPerHour(-1)));
47.255 + conn.println(".");
47.256 }
47.257 else if(commands[2].equalsIgnoreCase("GATEWAYED_NEWS_PER_HOUR"))
47.258 {
47.259 - printStatus(200, "gatewayed news per hour");
47.260 - println(Double.toString(Stats.getInstance().gatewayedPerHour(-1)));
47.261 - println(".");
47.262 + conn.println("100 gatewayed news per hour");
47.263 + conn.println(Double.toString(Stats.getInstance().gatewayedPerHour(-1)));
47.264 + conn.println(".");
47.265 }
47.266 else
47.267 {
47.268 - printStatus(501, "unknown sub command");
47.269 + conn.println("401 unknown sub command");
47.270 }
47.271 }
47.272 else
47.273 {
47.274 - printStatus(500, "invalid command usage");
47.275 + conn.println("400 invalid command usage");
47.276 }
47.277 }
47.278 else
47.279 {
47.280 - printStatus(500, "not allowed");
47.281 + conn.println("501 not allowed");
47.282 }
47.283 }
47.284
48.1 --- a/org/sonews/daemon/command/XPatCommand.java Wed Jul 01 10:48:22 2009 +0200
48.2 +++ b/org/sonews/daemon/command/XPatCommand.java Wed Jul 22 14:04:05 2009 +0200
48.3 @@ -19,8 +19,13 @@
48.4 package org.sonews.daemon.command;
48.5
48.6 import java.io.IOException;
48.7 -import java.sql.SQLException;
48.8 +import java.util.List;
48.9 +import java.util.Locale;
48.10 +import java.util.regex.PatternSyntaxException;
48.11 import org.sonews.daemon.NNTPConnection;
48.12 +import org.sonews.storage.StorageBackendException;
48.13 +import org.sonews.storage.StorageManager;
48.14 +import org.sonews.util.Pair;
48.15
48.16 /**
48.17 * <pre>
48.18 @@ -59,18 +64,24 @@
48.19 * 221 Header follows
48.20 * 430 no such article
48.21 * 502 no permission
48.22 + *
48.23 + * Response Data:
48.24 + *
48.25 + * art_nr fitting_header_value
48.26 + *
48.27 * </pre>
48.28 * [Source:"draft-ietf-nntp-imp-02.txt"] [Copyright: 1998 S. Barber]
48.29 *
48.30 * @author Christian Lins
48.31 * @since sonews/0.5.0
48.32 */
48.33 -public class XPatCommand extends AbstractCommand
48.34 +public class XPatCommand implements Command
48.35 {
48.36
48.37 - public XPatCommand(final NNTPConnection conn)
48.38 + @Override
48.39 + public String[] getSupportedCommandStrings()
48.40 {
48.41 - super(conn);
48.42 + return new String[]{"XPAT"};
48.43 }
48.44
48.45 @Override
48.46 @@ -80,10 +91,74 @@
48.47 }
48.48
48.49 @Override
48.50 - public void processLine(final String line)
48.51 - throws IOException, SQLException
48.52 + public boolean isStateful()
48.53 {
48.54 - printStatus(500, "not (yet) supported");
48.55 + return false;
48.56 + }
48.57 +
48.58 + @Override
48.59 + public void processLine(NNTPConnection conn, final String line, byte[] raw)
48.60 + throws IOException, StorageBackendException
48.61 + {
48.62 + if(conn.getCurrentChannel() == null)
48.63 + {
48.64 + conn.println("430 no group selected");
48.65 + return;
48.66 + }
48.67 +
48.68 + String[] command = line.split("\\p{Space}+");
48.69 +
48.70 + // There may be multiple patterns and Thunderbird produces
48.71 + // additional spaces between range and pattern
48.72 + if(command.length >= 4)
48.73 + {
48.74 + String header = command[1].toLowerCase(Locale.US);
48.75 + String range = command[2];
48.76 + String pattern = command[3];
48.77 +
48.78 + long start = -1;
48.79 + long end = -1;
48.80 + if(range.contains("-"))
48.81 + {
48.82 + String[] rsplit = range.split("-", 2);
48.83 + start = Long.parseLong(rsplit[0]);
48.84 + if(rsplit[1].length() > 0)
48.85 + {
48.86 + end = Long.parseLong(rsplit[1]);
48.87 + }
48.88 + }
48.89 + else // TODO: Handle Message-IDs
48.90 + {
48.91 + start = Long.parseLong(range);
48.92 + }
48.93 +
48.94 + try
48.95 + {
48.96 + List<Pair<Long, String>> heads = StorageManager.current().
48.97 + getArticleHeaders(conn.getCurrentChannel(), start, end, header, pattern);
48.98 +
48.99 + conn.println("221 header follows");
48.100 + for(Pair<Long, String> head : heads)
48.101 + {
48.102 + conn.println(head.getA() + " " + head.getB());
48.103 + }
48.104 + conn.println(".");
48.105 + }
48.106 + catch(PatternSyntaxException ex)
48.107 + {
48.108 + ex.printStackTrace();
48.109 + conn.println("500 invalid pattern syntax");
48.110 + }
48.111 + catch(StorageBackendException ex)
48.112 + {
48.113 + ex.printStackTrace();
48.114 + conn.println("500 internal server error");
48.115 + }
48.116 + }
48.117 + else
48.118 + {
48.119 + conn.println("430 invalid command usage");
48.120 + }
48.121 }
48.122
48.123 }
49.1 --- a/org/sonews/daemon/storage/Article.java Wed Jul 01 10:48:22 2009 +0200
49.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
49.3 @@ -1,401 +0,0 @@
49.4 -/*
49.5 - * SONEWS News Server
49.6 - * see AUTHORS for the list of contributors
49.7 - *
49.8 - * This program is free software: you can redistribute it and/or modify
49.9 - * it under the terms of the GNU General Public License as published by
49.10 - * the Free Software Foundation, either version 3 of the License, or
49.11 - * (at your option) any later version.
49.12 - *
49.13 - * This program is distributed in the hope that it will be useful,
49.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
49.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
49.16 - * GNU General Public License for more details.
49.17 - *
49.18 - * You should have received a copy of the GNU General Public License
49.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
49.20 - */
49.21 -
49.22 -package org.sonews.daemon.storage;
49.23 -
49.24 -import org.sonews.daemon.Config;
49.25 -import java.io.BufferedReader;
49.26 -import java.io.ByteArrayInputStream;
49.27 -import java.io.IOException;
49.28 -import java.io.InputStream;
49.29 -import java.io.InputStreamReader;
49.30 -import java.nio.charset.Charset;
49.31 -import java.sql.SQLException;
49.32 -import java.util.UUID;
49.33 -import java.util.ArrayList;
49.34 -import java.util.Enumeration;
49.35 -import java.util.List;
49.36 -import javax.mail.Header;
49.37 -import javax.mail.Message;
49.38 -import javax.mail.MessagingException;
49.39 -import javax.mail.Multipart;
49.40 -import javax.mail.internet.InternetHeaders;
49.41 -import javax.mail.internet.MimeUtility;
49.42 -import org.sonews.util.Log;
49.43 -
49.44 -/**
49.45 - * Represents a newsgroup article.
49.46 - * @author Christian Lins
49.47 - * @author Denis Schwerdel
49.48 - * @since n3tpd/0.1
49.49 - */
49.50 -public class Article extends ArticleHead
49.51 -{
49.52 -
49.53 - /**
49.54 - * Loads the Article identified by the given ID from the Database.
49.55 - * @param messageID
49.56 - * @return null if Article is not found or if an error occurred.
49.57 - */
49.58 - public static Article getByMessageID(final String messageID)
49.59 - {
49.60 - try
49.61 - {
49.62 - return Database.getInstance().getArticle(messageID);
49.63 - }
49.64 - catch(SQLException ex)
49.65 - {
49.66 - ex.printStackTrace();
49.67 - return null;
49.68 - }
49.69 - }
49.70 -
49.71 - public static Article getByArticleNumber(long articleIndex, Group group)
49.72 - throws SQLException
49.73 - {
49.74 - return Database.getInstance().getArticle(articleIndex, group.getID());
49.75 - }
49.76 -
49.77 - private String body = "";
49.78 - private String headerSrc = null;
49.79 -
49.80 - /**
49.81 - * Default constructor.
49.82 - */
49.83 - public Article()
49.84 - {
49.85 - }
49.86 -
49.87 - /**
49.88 - * Creates a new Article object using the date from the given
49.89 - * raw data.
49.90 - * This construction has only package visibility.
49.91 - */
49.92 - Article(String headers, String body)
49.93 - {
49.94 - try
49.95 - {
49.96 - this.body = body;
49.97 -
49.98 - // Parse the header
49.99 - this.headers = new InternetHeaders(
49.100 - new ByteArrayInputStream(headers.getBytes()));
49.101 -
49.102 - this.headerSrc = headers;
49.103 - }
49.104 - catch(MessagingException ex)
49.105 - {
49.106 - ex.printStackTrace();
49.107 - }
49.108 - }
49.109 -
49.110 - /**
49.111 - * Creates an Article instance using the data from the javax.mail.Message
49.112 - * object.
49.113 - * @see javax.mail.Message
49.114 - * @param msg
49.115 - * @throws IOException
49.116 - * @throws MessagingException
49.117 - */
49.118 - public Article(final Message msg)
49.119 - throws IOException, MessagingException
49.120 - {
49.121 - this.headers = new InternetHeaders();
49.122 -
49.123 - for(Enumeration e = msg.getAllHeaders() ; e.hasMoreElements();)
49.124 - {
49.125 - final Header header = (Header)e.nextElement();
49.126 - this.headers.addHeader(header.getName(), header.getValue());
49.127 - }
49.128 -
49.129 - // The "content" of the message can be a String if it's a simple text/plain
49.130 - // message, a Multipart object or an InputStream if the content is unknown.
49.131 - final Object content = msg.getContent();
49.132 - if(content instanceof String)
49.133 - {
49.134 - this.body = (String)content;
49.135 - }
49.136 - else if(content instanceof Multipart) // probably subclass MimeMultipart
49.137 - {
49.138 - // We're are not interested in the different parts of the MultipartMessage,
49.139 - // so we simply read in all data which *can* be huge.
49.140 - InputStream in = msg.getInputStream();
49.141 - this.body = readContent(in);
49.142 - }
49.143 - else if(content instanceof InputStream)
49.144 - {
49.145 - // The message format is unknown to the Message class, but we can
49.146 - // simply read in the whole message data.
49.147 - this.body = readContent((InputStream)content);
49.148 - }
49.149 - else
49.150 - {
49.151 - // Unknown content is probably a malformed mail we should skip.
49.152 - // On the other hand we produce an inconsistent mail mirror, but no
49.153 - // mail system must transport invalid content.
49.154 - Log.msg("Skipping message due to unknown content. Throwing exception...", true);
49.155 - throw new MessagingException("Unknown content: " + content);
49.156 - }
49.157 -
49.158 - // Validate headers
49.159 - validateHeaders();
49.160 - }
49.161 -
49.162 - /**
49.163 - * Reads lines from the given InputString into a String object.
49.164 - * TODO: Move this generalized method to org.sonews.util.io.Resource.
49.165 - * @param in
49.166 - * @return
49.167 - * @throws IOException
49.168 - */
49.169 - private String readContent(InputStream in)
49.170 - throws IOException
49.171 - {
49.172 - StringBuilder buf = new StringBuilder();
49.173 -
49.174 - BufferedReader rin = new BufferedReader(new InputStreamReader(in));
49.175 - String line = rin.readLine();
49.176 - while(line != null)
49.177 - {
49.178 - buf.append('\n');
49.179 - buf.append(line);
49.180 - line = rin.readLine();
49.181 - }
49.182 -
49.183 - return buf.toString();
49.184 - }
49.185 -
49.186 - /**
49.187 - * Removes the header identified by the given key.
49.188 - * @param headerKey
49.189 - */
49.190 - public void removeHeader(final String headerKey)
49.191 - {
49.192 - this.headers.removeHeader(headerKey);
49.193 - this.headerSrc = null;
49.194 - }
49.195 -
49.196 - /**
49.197 - * Generates a message id for this article and sets it into
49.198 - * the header object. You have to update the Database manually to make this
49.199 - * change persistent.
49.200 - * Note: a Message-ID should never be changed and only generated once.
49.201 - */
49.202 - private String generateMessageID()
49.203 - {
49.204 - String msgID = "<" + UUID.randomUUID() + "@"
49.205 - + Config.getInstance().get(Config.HOSTNAME, "localhost") + ">";
49.206 -
49.207 - this.headers.setHeader(Headers.MESSAGE_ID, msgID);
49.208 -
49.209 - return msgID;
49.210 - }
49.211 -
49.212 - /**
49.213 - * Returns the body string.
49.214 - */
49.215 - public String getBody()
49.216 - {
49.217 - return body;
49.218 - }
49.219 -
49.220 - /**
49.221 - * @return Charset of the body text
49.222 - */
49.223 - public Charset getBodyCharset()
49.224 - {
49.225 - // We espect something like
49.226 - // Content-Type: text/plain; charset=ISO-8859-15
49.227 - String contentType = getHeader(Headers.CONTENT_TYPE)[0];
49.228 - int idxCharsetStart = contentType.indexOf("charset=") + "charset=".length();
49.229 - int idxCharsetEnd = contentType.indexOf(";", idxCharsetStart);
49.230 -
49.231 - String charsetName = "UTF-8";
49.232 - if(idxCharsetStart >= 0 && idxCharsetStart < contentType.length())
49.233 - {
49.234 - if(idxCharsetEnd < 0)
49.235 - {
49.236 - charsetName = contentType.substring(idxCharsetStart);
49.237 - }
49.238 - else
49.239 - {
49.240 - charsetName = contentType.substring(idxCharsetStart, idxCharsetEnd);
49.241 - }
49.242 - }
49.243 -
49.244 - // Sometimes there are '"' around the name
49.245 - if(charsetName.length() > 2 &&
49.246 - charsetName.charAt(0) == '"' && charsetName.endsWith("\""))
49.247 - {
49.248 - charsetName = charsetName.substring(1, charsetName.length() - 2);
49.249 - }
49.250 -
49.251 - // Create charset
49.252 - Charset charset = Charset.forName("UTF-8"); // This MUST be supported by JVM
49.253 - try
49.254 - {
49.255 - charset = Charset.forName(charsetName);
49.256 - }
49.257 - catch(Exception ex)
49.258 - {
49.259 - Log.msg(ex.getMessage(), false);
49.260 - Log.msg("Article.getBodyCharset(): Unknown charset: " + charsetName, false);
49.261 - }
49.262 - return charset;
49.263 - }
49.264 -
49.265 - /**
49.266 - * @return Numerical IDs of the newsgroups this Article belongs to.
49.267 - */
49.268 - List<Group> getGroups()
49.269 - {
49.270 - String[] groupnames = getHeader(Headers.NEWSGROUPS)[0].split(",");
49.271 - ArrayList<Group> groups = new ArrayList<Group>();
49.272 -
49.273 - try
49.274 - {
49.275 - for(String newsgroup : groupnames)
49.276 - {
49.277 - newsgroup = newsgroup.trim();
49.278 - Group group = Database.getInstance().getGroup(newsgroup);
49.279 - if(group != null && // If the server does not provide the group, ignore it
49.280 - !groups.contains(group)) // Yes, there may be duplicates
49.281 - {
49.282 - groups.add(group);
49.283 - }
49.284 - }
49.285 - }
49.286 - catch (SQLException ex)
49.287 - {
49.288 - ex.printStackTrace();
49.289 - return null;
49.290 - }
49.291 - return groups;
49.292 - }
49.293 -
49.294 - public void setBody(String body)
49.295 - {
49.296 - this.body = body;
49.297 - }
49.298 -
49.299 - /**
49.300 - *
49.301 - * @param groupname Name(s) of newsgroups
49.302 - */
49.303 - public void setGroup(String groupname)
49.304 - {
49.305 - this.headers.setHeader(Headers.NEWSGROUPS, groupname);
49.306 - }
49.307 -
49.308 - public String getMessageID()
49.309 - {
49.310 - String[] msgID = getHeader(Headers.MESSAGE_ID);
49.311 - return msgID[0];
49.312 - }
49.313 -
49.314 - public Enumeration getAllHeaders()
49.315 - {
49.316 - return this.headers.getAllHeaders();
49.317 - }
49.318 -
49.319 - /**
49.320 - * @return Header source code of this Article.
49.321 - */
49.322 - public String getHeaderSource()
49.323 - {
49.324 - if(this.headerSrc != null)
49.325 - {
49.326 - return this.headerSrc;
49.327 - }
49.328 -
49.329 - StringBuffer buf = new StringBuffer();
49.330 -
49.331 - for(Enumeration en = this.headers.getAllHeaders(); en.hasMoreElements();)
49.332 - {
49.333 - Header entry = (Header)en.nextElement();
49.334 -
49.335 - buf.append(entry.getName());
49.336 - buf.append(": ");
49.337 - buf.append(
49.338 - MimeUtility.fold(entry.getName().length() + 2, entry.getValue()));
49.339 -
49.340 - if(en.hasMoreElements())
49.341 - {
49.342 - buf.append("\r\n");
49.343 - }
49.344 - }
49.345 -
49.346 - this.headerSrc = buf.toString();
49.347 - return this.headerSrc;
49.348 - }
49.349 -
49.350 - public long getIndexInGroup(Group group)
49.351 - throws SQLException
49.352 - {
49.353 - return Database.getInstance().getArticleIndex(this, group);
49.354 - }
49.355 -
49.356 - /**
49.357 - * Sets the headers of this Article. If headers contain no
49.358 - * Message-Id a new one is created.
49.359 - * @param headers
49.360 - */
49.361 - public void setHeaders(InternetHeaders headers)
49.362 - {
49.363 - this.headers = headers;
49.364 - validateHeaders();
49.365 - }
49.366 -
49.367 - /**
49.368 - * @return String containing the Message-ID.
49.369 - */
49.370 - @Override
49.371 - public String toString()
49.372 - {
49.373 - return getMessageID();
49.374 - }
49.375 -
49.376 - /**
49.377 - * Checks some headers for their validity and generates an
49.378 - * appropriate Path-header for this host if not yet existing.
49.379 - * This method is called by some Article constructors and the
49.380 - * method setHeaders().
49.381 - * @return true if something on the headers was changed.
49.382 - */
49.383 - private void validateHeaders()
49.384 - {
49.385 - // Check for valid Path-header
49.386 - final String path = getHeader(Headers.PATH)[0];
49.387 - final String host = Config.getInstance().get(Config.HOSTNAME, "localhost");
49.388 - if(!path.startsWith(host))
49.389 - {
49.390 - StringBuffer pathBuf = new StringBuffer();
49.391 - pathBuf.append(host);
49.392 - pathBuf.append('!');
49.393 - pathBuf.append(path);
49.394 - this.headers.setHeader(Headers.PATH, pathBuf.toString());
49.395 - }
49.396 -
49.397 - // Generate a messageID if no one is existing
49.398 - if(getMessageID().equals(""))
49.399 - {
49.400 - generateMessageID();
49.401 - }
49.402 - }
49.403 -
49.404 -}
50.1 --- a/org/sonews/daemon/storage/ArticleHead.java Wed Jul 01 10:48:22 2009 +0200
50.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
50.3 @@ -1,78 +0,0 @@
50.4 -/*
50.5 - * SONEWS News Server
50.6 - * see AUTHORS for the list of contributors
50.7 - *
50.8 - * This program is free software: you can redistribute it and/or modify
50.9 - * it under the terms of the GNU General Public License as published by
50.10 - * the Free Software Foundation, either version 3 of the License, or
50.11 - * (at your option) any later version.
50.12 - *
50.13 - * This program is distributed in the hope that it will be useful,
50.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
50.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
50.16 - * GNU General Public License for more details.
50.17 - *
50.18 - * You should have received a copy of the GNU General Public License
50.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
50.20 - */
50.21 -
50.22 -package org.sonews.daemon.storage;
50.23 -
50.24 -import java.io.ByteArrayInputStream;
50.25 -import javax.mail.MessagingException;
50.26 -import javax.mail.internet.InternetHeaders;
50.27 -
50.28 -/**
50.29 - * An article with no body only headers.
50.30 - * @author Christian Lins
50.31 - * @since sonews/0.5.0
50.32 - */
50.33 -public class ArticleHead
50.34 -{
50.35 -
50.36 - protected InternetHeaders headers;
50.37 -
50.38 - protected ArticleHead()
50.39 - {
50.40 - }
50.41 -
50.42 - public ArticleHead(String headers)
50.43 - {
50.44 - try
50.45 - {
50.46 - // Parse the header
50.47 - this.headers = new InternetHeaders(
50.48 - new ByteArrayInputStream(headers.getBytes()));
50.49 - }
50.50 - catch(MessagingException ex)
50.51 - {
50.52 - ex.printStackTrace();
50.53 - }
50.54 - }
50.55 -
50.56 - /**
50.57 - * Returns the header field with given name.
50.58 - * @param name
50.59 - * @return Header values or empty string.
50.60 - */
50.61 - public String[] getHeader(String name)
50.62 - {
50.63 - String[] ret = this.headers.getHeader(name);
50.64 - if(ret == null)
50.65 - {
50.66 - ret = new String[]{""};
50.67 - }
50.68 - return ret;
50.69 - }
50.70 -
50.71 - /**
50.72 - * Sets the header value identified through the header name.
50.73 - * @param name
50.74 - * @param value
50.75 - */
50.76 - public void setHeader(String name, String value)
50.77 - {
50.78 - this.headers.setHeader(name, value);
50.79 - }
50.80 -
50.81 -}
51.1 --- a/org/sonews/daemon/storage/Database.java Wed Jul 01 10:48:22 2009 +0200
51.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
51.3 @@ -1,1352 +0,0 @@
51.4 -/*
51.5 - * SONEWS News Server
51.6 - * see AUTHORS for the list of contributors
51.7 - *
51.8 - * This program is free software: you can redistribute it and/or modify
51.9 - * it under the terms of the GNU General Public License as published by
51.10 - * the Free Software Foundation, either version 3 of the License, or
51.11 - * (at your option) any later version.
51.12 - *
51.13 - * This program is distributed in the hope that it will be useful,
51.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
51.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
51.16 - * GNU General Public License for more details.
51.17 - *
51.18 - * You should have received a copy of the GNU General Public License
51.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
51.20 - */
51.21 -
51.22 -package org.sonews.daemon.storage;
51.23 -
51.24 -import java.sql.Connection;
51.25 -import java.sql.DriverManager;
51.26 -import java.sql.ResultSet;
51.27 -import java.sql.SQLException;
51.28 -import java.sql.Statement;
51.29 -import java.sql.PreparedStatement;
51.30 -import java.util.ArrayList;
51.31 -import java.util.Enumeration;
51.32 -import java.util.List;
51.33 -import java.util.Map;
51.34 -import java.util.concurrent.ConcurrentHashMap;
51.35 -import javax.mail.Header;
51.36 -import javax.mail.internet.InternetAddress;
51.37 -import javax.mail.internet.MimeUtility;
51.38 -import org.sonews.daemon.BootstrapConfig;
51.39 -import org.sonews.util.Log;
51.40 -import org.sonews.feed.Subscription;
51.41 -import org.sonews.util.Pair;
51.42 -
51.43 -/**
51.44 - * Database facade class.
51.45 - * @author Christian Lins
51.46 - * @since sonews/0.5.0
51.47 - */
51.48 -// TODO: Refactor this class to reduce size (e.g. ArticleDatabase GroupDatabase)
51.49 -public class Database
51.50 -{
51.51 -
51.52 - public static final int MAX_RESTARTS = 3;
51.53 -
51.54 - private static final Map<Thread, Database> instances
51.55 - = new ConcurrentHashMap<Thread, Database>();
51.56 -
51.57 - /**
51.58 - * @return Instance of the current Database backend. Returns null if an error
51.59 - * has occurred.
51.60 - */
51.61 - public static Database getInstance(boolean create)
51.62 - throws SQLException
51.63 - {
51.64 - if(!instances.containsKey(Thread.currentThread()) && create)
51.65 - {
51.66 - Database db = new Database();
51.67 - db.arise();
51.68 - instances.put(Thread.currentThread(), db);
51.69 - return db;
51.70 - }
51.71 - else
51.72 - {
51.73 - return instances.get(Thread.currentThread());
51.74 - }
51.75 - }
51.76 -
51.77 - public static Database getInstance()
51.78 - throws SQLException
51.79 - {
51.80 - return getInstance(true);
51.81 - }
51.82 -
51.83 - private Connection conn = null;
51.84 - private PreparedStatement pstmtAddArticle1 = null;
51.85 - private PreparedStatement pstmtAddArticle2 = null;
51.86 - private PreparedStatement pstmtAddArticle3 = null;
51.87 - private PreparedStatement pstmtAddArticle4 = null;
51.88 - private PreparedStatement pstmtAddGroup0 = null;
51.89 - private PreparedStatement pstmtAddEvent = null;
51.90 - private PreparedStatement pstmtCountArticles = null;
51.91 - private PreparedStatement pstmtCountGroups = null;
51.92 - private PreparedStatement pstmtDeleteArticle0 = null;
51.93 - private PreparedStatement pstmtGetArticle0 = null;
51.94 - private PreparedStatement pstmtGetArticle1 = null;
51.95 - private PreparedStatement pstmtGetArticleHeaders = null;
51.96 - private PreparedStatement pstmtGetArticleHeads = null;
51.97 - private PreparedStatement pstmtGetArticleIDs = null;
51.98 - private PreparedStatement pstmtGetArticleIndex = null;
51.99 - private PreparedStatement pstmtGetConfigValue = null;
51.100 - private PreparedStatement pstmtGetEventsCount0 = null;
51.101 - private PreparedStatement pstmtGetEventsCount1 = null;
51.102 - private PreparedStatement pstmtGetGroupForList = null;
51.103 - private PreparedStatement pstmtGetGroup0 = null;
51.104 - private PreparedStatement pstmtGetGroup1 = null;
51.105 - private PreparedStatement pstmtGetFirstArticleNumber = null;
51.106 - private PreparedStatement pstmtGetListForGroup = null;
51.107 - private PreparedStatement pstmtGetLastArticleNumber = null;
51.108 - private PreparedStatement pstmtGetMaxArticleID = null;
51.109 - private PreparedStatement pstmtGetMaxArticleIndex = null;
51.110 - private PreparedStatement pstmtGetPostingsCount = null;
51.111 - private PreparedStatement pstmtGetSubscriptions = null;
51.112 - private PreparedStatement pstmtIsArticleExisting = null;
51.113 - private PreparedStatement pstmtIsGroupExisting = null;
51.114 - private PreparedStatement pstmtSetConfigValue0 = null;
51.115 - private PreparedStatement pstmtSetConfigValue1 = null;
51.116 -
51.117 - /** How many times the database connection was reinitialized */
51.118 - private int restarts = 0;
51.119 -
51.120 - /**
51.121 - * Rises the database: reconnect and recreate all prepared statements.
51.122 - * @throws java.lang.SQLException
51.123 - */
51.124 - private void arise()
51.125 - throws SQLException
51.126 - {
51.127 - try
51.128 - {
51.129 - // Load database driver
51.130 - Class.forName(
51.131 - BootstrapConfig.getInstance().get(BootstrapConfig.STORAGE_DBMSDRIVER, "java.lang.Object"));
51.132 -
51.133 - // Establish database connection
51.134 - this.conn = DriverManager.getConnection(
51.135 - BootstrapConfig.getInstance().get(BootstrapConfig.STORAGE_DATABASE, "<not specified>"),
51.136 - BootstrapConfig.getInstance().get(BootstrapConfig.STORAGE_USER, "root"),
51.137 - BootstrapConfig.getInstance().get(BootstrapConfig.STORAGE_PASSWORD, ""));
51.138 -
51.139 - this.conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
51.140 - if(this.conn.getTransactionIsolation() != Connection.TRANSACTION_SERIALIZABLE)
51.141 - {
51.142 - Log.msg("Warning: Database is NOT fully serializable!", false);
51.143 - }
51.144 -
51.145 - // Prepare statements for method addArticle()
51.146 - this.pstmtAddArticle1 = conn.prepareStatement(
51.147 - "INSERT INTO articles (article_id, body) VALUES(?, ?)");
51.148 - this.pstmtAddArticle2 = conn.prepareStatement(
51.149 - "INSERT INTO headers (article_id, header_key, header_value, header_index) " +
51.150 - "VALUES (?, ?, ?, ?)");
51.151 - this.pstmtAddArticle3 = conn.prepareStatement(
51.152 - "INSERT INTO postings (group_id, article_id, article_index)" +
51.153 - "VALUES (?, ?, ?)");
51.154 - this.pstmtAddArticle4 = conn.prepareStatement(
51.155 - "INSERT INTO article_ids (article_id, message_id) VALUES (?, ?)");
51.156 -
51.157 - // Prepare statement for method addStatValue()
51.158 - this.pstmtAddEvent = conn.prepareStatement(
51.159 - "INSERT INTO events VALUES (?, ?, ?)");
51.160 -
51.161 - // Prepare statement for method addGroup()
51.162 - this.pstmtAddGroup0 = conn.prepareStatement(
51.163 - "INSERT INTO groups (name, flags) VALUES (?, ?)");
51.164 -
51.165 - // Prepare statement for method countArticles()
51.166 - this.pstmtCountArticles = conn.prepareStatement(
51.167 - "SELECT Count(article_id) FROM article_ids");
51.168 -
51.169 - // Prepare statement for method countGroups()
51.170 - this.pstmtCountGroups = conn.prepareStatement(
51.171 - "SELECT Count(group_id) FROM groups WHERE " +
51.172 - "flags & " + Group.DELETED + " = 0");
51.173 -
51.174 - // Prepare statements for method delete(article)
51.175 - this.pstmtDeleteArticle0 = conn.prepareStatement(
51.176 - "DELETE FROM articles WHERE article_id = " +
51.177 - "(SELECT article_id FROM article_ids WHERE message_id = ?)");
51.178 -
51.179 - // Prepare statements for methods getArticle()
51.180 - this.pstmtGetArticle0 = conn.prepareStatement(
51.181 - "SELECT * FROM articles WHERE article_id = " +
51.182 - "(SELECT article_id FROM article_ids WHERE message_id = ?)");
51.183 - this.pstmtGetArticle1 = conn.prepareStatement(
51.184 - "SELECT * FROM articles WHERE article_id = " +
51.185 - "(SELECT article_id FROM postings WHERE " +
51.186 - "article_index = ? AND group_id = ?)");
51.187 -
51.188 - // Prepare statement for method getArticleHeaders()
51.189 - this.pstmtGetArticleHeaders = conn.prepareStatement(
51.190 - "SELECT header_key, header_value FROM headers WHERE article_id = ? " +
51.191 - "ORDER BY header_index ASC");
51.192 -
51.193 - this.pstmtGetArticleIDs = conn.prepareStatement(
51.194 - "SELECT article_index FROM postings WHERE group_id = ?");
51.195 -
51.196 - // Prepare statement for method getArticleIndex
51.197 - this.pstmtGetArticleIndex = conn.prepareStatement(
51.198 - "SELECT article_index FROM postings WHERE " +
51.199 - "article_id = (SELECT article_id FROM article_ids " +
51.200 - "WHERE message_id = ?) " +
51.201 - " AND group_id = ?");
51.202 -
51.203 - // Prepare statements for method getArticleHeads()
51.204 - this.pstmtGetArticleHeads = conn.prepareStatement(
51.205 - "SELECT article_id, article_index FROM postings WHERE " +
51.206 - "postings.group_id = ? AND article_index >= ? AND " +
51.207 - "article_index <= ?");
51.208 -
51.209 - // Prepare statements for method getConfigValue()
51.210 - this.pstmtGetConfigValue = conn.prepareStatement(
51.211 - "SELECT config_value FROM config WHERE config_key = ?");
51.212 -
51.213 - // Prepare statements for method getEventsCount()
51.214 - this.pstmtGetEventsCount0 = conn.prepareStatement(
51.215 - "SELECT Count(*) FROM events WHERE event_key = ? AND " +
51.216 - "event_time >= ? AND event_time < ?");
51.217 -
51.218 - this.pstmtGetEventsCount1 = conn.prepareStatement(
51.219 - "SELECT Count(*) FROM events WHERE event_key = ? AND " +
51.220 - "event_time >= ? AND event_time < ? AND group_id = ?");
51.221 -
51.222 - // Prepare statement for method getGroupForList()
51.223 - this.pstmtGetGroupForList = conn.prepareStatement(
51.224 - "SELECT name FROM groups INNER JOIN groups2list " +
51.225 - "ON groups.group_id = groups2list.group_id " +
51.226 - "WHERE groups2list.listaddress = ?");
51.227 -
51.228 - // Prepare statement for method getGroup()
51.229 - this.pstmtGetGroup0 = conn.prepareStatement(
51.230 - "SELECT group_id, flags FROM groups WHERE Name = ?");
51.231 - this.pstmtGetGroup1 = conn.prepareStatement(
51.232 - "SELECT name FROM groups WHERE group_id = ?");
51.233 -
51.234 - // Prepare statement for method getLastArticleNumber()
51.235 - this.pstmtGetLastArticleNumber = conn.prepareStatement(
51.236 - "SELECT Max(article_index) FROM postings WHERE group_id = ?");
51.237 -
51.238 - // Prepare statement for method getListForGroup()
51.239 - this.pstmtGetListForGroup = conn.prepareStatement(
51.240 - "SELECT listaddress FROM groups2list INNER JOIN groups " +
51.241 - "ON groups.group_id = groups2list.group_id WHERE name = ?");
51.242 -
51.243 - // Prepare statement for method getMaxArticleID()
51.244 - this.pstmtGetMaxArticleID = conn.prepareStatement(
51.245 - "SELECT Max(article_id) FROM articles");
51.246 -
51.247 - // Prepare statement for method getMaxArticleIndex()
51.248 - this.pstmtGetMaxArticleIndex = conn.prepareStatement(
51.249 - "SELECT Max(article_index) FROM postings WHERE group_id = ?");
51.250 -
51.251 - // Prepare statement for method getFirstArticleNumber()
51.252 - this.pstmtGetFirstArticleNumber = conn.prepareStatement(
51.253 - "SELECT Min(article_index) FROM postings WHERE group_id = ?");
51.254 -
51.255 - // Prepare statement for method getPostingsCount()
51.256 - this.pstmtGetPostingsCount = conn.prepareStatement(
51.257 - "SELECT Count(*) FROM postings NATURAL JOIN groups " +
51.258 - "WHERE groups.name = ?");
51.259 -
51.260 - // Prepare statement for method getSubscriptions()
51.261 - this.pstmtGetSubscriptions = conn.prepareStatement(
51.262 - "SELECT host, port, name FROM peers NATURAL JOIN " +
51.263 - "peer_subscriptions NATURAL JOIN groups WHERE feedtype = ?");
51.264 -
51.265 - // Prepare statement for method isArticleExisting()
51.266 - this.pstmtIsArticleExisting = conn.prepareStatement(
51.267 - "SELECT Count(article_id) FROM article_ids WHERE message_id = ?");
51.268 -
51.269 - // Prepare statement for method isGroupExisting()
51.270 - this.pstmtIsGroupExisting = conn.prepareStatement(
51.271 - "SELECT * FROM groups WHERE name = ?");
51.272 -
51.273 - // Prepare statement for method setConfigValue()
51.274 - this.pstmtSetConfigValue0 = conn.prepareStatement(
51.275 - "DELETE FROM config WHERE config_key = ?");
51.276 - this.pstmtSetConfigValue1 = conn.prepareStatement(
51.277 - "INSERT INTO config VALUES(?, ?)");
51.278 - }
51.279 - catch(ClassNotFoundException ex)
51.280 - {
51.281 - throw new Error("JDBC Driver not found!", ex);
51.282 - }
51.283 - }
51.284 -
51.285 - /**
51.286 - * Adds an article to the database.
51.287 - * @param article
51.288 - * @return
51.289 - * @throws java.sql.SQLException
51.290 - */
51.291 - public void addArticle(final Article article)
51.292 - throws SQLException
51.293 - {
51.294 - try
51.295 - {
51.296 - this.conn.setAutoCommit(false);
51.297 -
51.298 - int newArticleID = getMaxArticleID() + 1;
51.299 -
51.300 - // Fill prepared statement with values;
51.301 - // writes body to article table
51.302 - pstmtAddArticle1.setInt(1, newArticleID);
51.303 - pstmtAddArticle1.setBytes(2, article.getBody().getBytes());
51.304 - pstmtAddArticle1.execute();
51.305 -
51.306 - // Add headers
51.307 - Enumeration headers = article.getAllHeaders();
51.308 - for(int n = 0; headers.hasMoreElements(); n++)
51.309 - {
51.310 - Header header = (Header)headers.nextElement();
51.311 - pstmtAddArticle2.setInt(1, newArticleID);
51.312 - pstmtAddArticle2.setString(2, header.getName().toLowerCase());
51.313 - pstmtAddArticle2.setString(3,
51.314 - header.getValue().replaceAll("[\r\n]", ""));
51.315 - pstmtAddArticle2.setInt(4, n);
51.316 - pstmtAddArticle2.execute();
51.317 - }
51.318 -
51.319 - // For each newsgroup add a reference
51.320 - List<Group> groups = article.getGroups();
51.321 - for(Group group : groups)
51.322 - {
51.323 - pstmtAddArticle3.setLong(1, group.getID());
51.324 - pstmtAddArticle3.setInt(2, newArticleID);
51.325 - pstmtAddArticle3.setLong(3, getMaxArticleIndex(group.getID()) + 1);
51.326 - pstmtAddArticle3.execute();
51.327 - }
51.328 -
51.329 - // Write message-id to article_ids table
51.330 - this.pstmtAddArticle4.setInt(1, newArticleID);
51.331 - this.pstmtAddArticle4.setString(2, article.getMessageID());
51.332 - this.pstmtAddArticle4.execute();
51.333 -
51.334 - this.conn.commit();
51.335 - this.conn.setAutoCommit(true);
51.336 -
51.337 - this.restarts = 0; // Reset error count
51.338 - }
51.339 - catch(SQLException ex)
51.340 - {
51.341 - try
51.342 - {
51.343 - this.conn.rollback(); // Rollback changes
51.344 - }
51.345 - catch(SQLException ex2)
51.346 - {
51.347 - Log.msg("Rollback of addArticle() failed: " + ex2, false);
51.348 - }
51.349 -
51.350 - try
51.351 - {
51.352 - this.conn.setAutoCommit(true); // and release locks
51.353 - }
51.354 - catch(SQLException ex2)
51.355 - {
51.356 - Log.msg("setAutoCommit(true) of addArticle() failed: " + ex2, false);
51.357 - }
51.358 -
51.359 - restartConnection(ex);
51.360 - addArticle(article);
51.361 - }
51.362 - }
51.363 -
51.364 - /**
51.365 - * Adds a group to the Database. This method is not accessible via NNTP.
51.366 - * @param name
51.367 - * @throws java.sql.SQLException
51.368 - */
51.369 - public void addGroup(String name, int flags)
51.370 - throws SQLException
51.371 - {
51.372 - try
51.373 - {
51.374 - this.conn.setAutoCommit(false);
51.375 - pstmtAddGroup0.setString(1, name);
51.376 - pstmtAddGroup0.setInt(2, flags);
51.377 -
51.378 - pstmtAddGroup0.executeUpdate();
51.379 - this.conn.commit();
51.380 - this.conn.setAutoCommit(true);
51.381 - this.restarts = 0; // Reset error count
51.382 - }
51.383 - catch(SQLException ex)
51.384 - {
51.385 - this.conn.rollback();
51.386 - this.conn.setAutoCommit(true);
51.387 - restartConnection(ex);
51.388 - addGroup(name, flags);
51.389 - }
51.390 - }
51.391 -
51.392 - public void addEvent(long time, byte type, long gid)
51.393 - throws SQLException
51.394 - {
51.395 - try
51.396 - {
51.397 - this.conn.setAutoCommit(false);
51.398 - this.pstmtAddEvent.setLong(1, time);
51.399 - this.pstmtAddEvent.setInt(2, type);
51.400 - this.pstmtAddEvent.setLong(3, gid);
51.401 - this.pstmtAddEvent.executeUpdate();
51.402 - this.conn.commit();
51.403 - this.conn.setAutoCommit(true);
51.404 - this.restarts = 0;
51.405 - }
51.406 - catch(SQLException ex)
51.407 - {
51.408 - this.conn.rollback();
51.409 - this.conn.setAutoCommit(true);
51.410 -
51.411 - restartConnection(ex);
51.412 - addEvent(time, type, gid);
51.413 - }
51.414 - }
51.415 -
51.416 - public int countArticles()
51.417 - throws SQLException
51.418 - {
51.419 - ResultSet rs = null;
51.420 -
51.421 - try
51.422 - {
51.423 - rs = this.pstmtCountArticles.executeQuery();
51.424 - if(rs.next())
51.425 - {
51.426 - return rs.getInt(1);
51.427 - }
51.428 - else
51.429 - {
51.430 - return -1;
51.431 - }
51.432 - }
51.433 - catch(SQLException ex)
51.434 - {
51.435 - restartConnection(ex);
51.436 - return countArticles();
51.437 - }
51.438 - finally
51.439 - {
51.440 - if(rs != null)
51.441 - {
51.442 - rs.close();
51.443 - restarts = 0;
51.444 - }
51.445 - }
51.446 - }
51.447 -
51.448 - public int countGroups()
51.449 - throws SQLException
51.450 - {
51.451 - ResultSet rs = null;
51.452 -
51.453 - try
51.454 - {
51.455 - rs = this.pstmtCountGroups.executeQuery();
51.456 - if(rs.next())
51.457 - {
51.458 - return rs.getInt(1);
51.459 - }
51.460 - else
51.461 - {
51.462 - return -1;
51.463 - }
51.464 - }
51.465 - catch(SQLException ex)
51.466 - {
51.467 - restartConnection(ex);
51.468 - return countGroups();
51.469 - }
51.470 - finally
51.471 - {
51.472 - if(rs != null)
51.473 - {
51.474 - rs.close();
51.475 - restarts = 0;
51.476 - }
51.477 - }
51.478 - }
51.479 -
51.480 - public void delete(final String messageID)
51.481 - throws SQLException
51.482 - {
51.483 - try
51.484 - {
51.485 - this.conn.setAutoCommit(false);
51.486 -
51.487 - this.pstmtDeleteArticle0.setString(1, messageID);
51.488 - int rs = this.pstmtDeleteArticle0.executeUpdate();
51.489 -
51.490 - // We trust the ON DELETE CASCADE functionality to delete
51.491 - // orphaned references
51.492 -
51.493 - this.conn.commit();
51.494 - this.conn.setAutoCommit(true);
51.495 - }
51.496 - catch(SQLException ex)
51.497 - {
51.498 - throw ex;
51.499 - }
51.500 - }
51.501 -
51.502 - public Article getArticle(String messageID)
51.503 - throws SQLException
51.504 - {
51.505 - ResultSet rs = null;
51.506 - try
51.507 - {
51.508 - pstmtGetArticle0.setString(1, messageID);
51.509 - rs = pstmtGetArticle0.executeQuery();
51.510 -
51.511 - if(!rs.next())
51.512 - {
51.513 - return null;
51.514 - }
51.515 - else
51.516 - {
51.517 - String body = new String(rs.getBytes("body"));
51.518 - String headers = getArticleHeaders(rs.getInt("article_id"));
51.519 - return new Article(headers, body);
51.520 - }
51.521 - }
51.522 - catch(SQLException ex)
51.523 - {
51.524 - restartConnection(ex);
51.525 - return getArticle(messageID);
51.526 - }
51.527 - finally
51.528 - {
51.529 - if(rs != null)
51.530 - {
51.531 - rs.close();
51.532 - restarts = 0; // Reset error count
51.533 - }
51.534 - }
51.535 - }
51.536 -
51.537 - /**
51.538 - * Retrieves an article by its ID.
51.539 - * @param articleID
51.540 - * @return
51.541 - * @throws java.sql.SQLException
51.542 - */
51.543 - public Article getArticle(long articleIndex, long gid)
51.544 - throws SQLException
51.545 - {
51.546 - ResultSet rs = null;
51.547 -
51.548 - try
51.549 - {
51.550 - this.pstmtGetArticle1.setLong(1, articleIndex);
51.551 - this.pstmtGetArticle1.setLong(2, gid);
51.552 -
51.553 - rs = this.pstmtGetArticle1.executeQuery();
51.554 -
51.555 - if(rs.next())
51.556 - {
51.557 - String body = new String(rs.getBytes("body"));
51.558 - String headers = getArticleHeaders(rs.getInt("article_id"));
51.559 - return new Article(headers, body);
51.560 - }
51.561 - else
51.562 - {
51.563 - return null;
51.564 - }
51.565 - }
51.566 - catch(SQLException ex)
51.567 - {
51.568 - restartConnection(ex);
51.569 - return getArticle(articleIndex, gid);
51.570 - }
51.571 - finally
51.572 - {
51.573 - if(rs != null)
51.574 - {
51.575 - rs.close();
51.576 - restarts = 0;
51.577 - }
51.578 - }
51.579 - }
51.580 -
51.581 - public String getArticleHeaders(long articleID)
51.582 - throws SQLException
51.583 - {
51.584 - ResultSet rs = null;
51.585 -
51.586 - try
51.587 - {
51.588 - this.pstmtGetArticleHeaders.setLong(1, articleID);
51.589 - rs = this.pstmtGetArticleHeaders.executeQuery();
51.590 -
51.591 - StringBuilder buf = new StringBuilder();
51.592 - if(rs.next())
51.593 - {
51.594 - for(;;)
51.595 - {
51.596 - buf.append(rs.getString(1)); // key
51.597 - buf.append(": ");
51.598 - String foldedValue = MimeUtility.fold(0, rs.getString(2));
51.599 - buf.append(foldedValue); // value
51.600 - if(rs.next())
51.601 - {
51.602 - buf.append("\r\n");
51.603 - }
51.604 - else
51.605 - {
51.606 - break;
51.607 - }
51.608 - }
51.609 - }
51.610 -
51.611 - return buf.toString();
51.612 - }
51.613 - catch(SQLException ex)
51.614 - {
51.615 - restartConnection(ex);
51.616 - return getArticleHeaders(articleID);
51.617 - }
51.618 - finally
51.619 - {
51.620 - if(rs != null)
51.621 - rs.close();
51.622 - }
51.623 - }
51.624 -
51.625 - public long getArticleIndex(Article article, Group group)
51.626 - throws SQLException
51.627 - {
51.628 - ResultSet rs = null;
51.629 -
51.630 - try
51.631 - {
51.632 - this.pstmtGetArticleIndex.setString(1, article.getMessageID());
51.633 - this.pstmtGetArticleIndex.setLong(2, group.getID());
51.634 -
51.635 - rs = this.pstmtGetArticleIndex.executeQuery();
51.636 - if(rs.next())
51.637 - {
51.638 - return rs.getLong(1);
51.639 - }
51.640 - else
51.641 - {
51.642 - return -1;
51.643 - }
51.644 - }
51.645 - catch(SQLException ex)
51.646 - {
51.647 - restartConnection(ex);
51.648 - return getArticleIndex(article, group);
51.649 - }
51.650 - finally
51.651 - {
51.652 - if(rs != null)
51.653 - rs.close();
51.654 - }
51.655 - }
51.656 -
51.657 - /**
51.658 - * Returns a list of Long/Article Pairs.
51.659 - * @throws java.sql.SQLException
51.660 - */
51.661 - public List<Pair<Long, ArticleHead>> getArticleHeads(Group group, int first, int last)
51.662 - throws SQLException
51.663 - {
51.664 - ResultSet rs = null;
51.665 -
51.666 - try
51.667 - {
51.668 - this.pstmtGetArticleHeads.setLong(1, group.getID());
51.669 - this.pstmtGetArticleHeads.setInt(2, first);
51.670 - this.pstmtGetArticleHeads.setInt(3, last);
51.671 - rs = pstmtGetArticleHeads.executeQuery();
51.672 -
51.673 - List<Pair<Long, ArticleHead>> articles
51.674 - = new ArrayList<Pair<Long, ArticleHead>>();
51.675 -
51.676 - while (rs.next())
51.677 - {
51.678 - long aid = rs.getLong("article_id");
51.679 - long aidx = rs.getLong("article_index");
51.680 - String headers = getArticleHeaders(aid);
51.681 - articles.add(new Pair<Long, ArticleHead>(aidx,
51.682 - new ArticleHead(headers)));
51.683 - }
51.684 -
51.685 - return articles;
51.686 - }
51.687 - catch(SQLException ex)
51.688 - {
51.689 - restartConnection(ex);
51.690 - return getArticleHeads(group, first, last);
51.691 - }
51.692 - finally
51.693 - {
51.694 - if(rs != null)
51.695 - rs.close();
51.696 - }
51.697 - }
51.698 -
51.699 - public List<Long> getArticleNumbers(long gid)
51.700 - throws SQLException
51.701 - {
51.702 - ResultSet rs = null;
51.703 - try
51.704 - {
51.705 - List<Long> ids = new ArrayList<Long>();
51.706 - this.pstmtGetArticleIDs.setLong(1, gid);
51.707 - rs = this.pstmtGetArticleIDs.executeQuery();
51.708 - while(rs.next())
51.709 - {
51.710 - ids.add(rs.getLong(1));
51.711 - }
51.712 - return ids;
51.713 - }
51.714 - catch(SQLException ex)
51.715 - {
51.716 - restartConnection(ex);
51.717 - return getArticleNumbers(gid);
51.718 - }
51.719 - finally
51.720 - {
51.721 - if(rs != null)
51.722 - {
51.723 - rs.close();
51.724 - restarts = 0; // Clear the restart count after successful request
51.725 - }
51.726 - }
51.727 - }
51.728 -
51.729 - public String getConfigValue(String key)
51.730 - throws SQLException
51.731 - {
51.732 - ResultSet rs = null;
51.733 - try
51.734 - {
51.735 - this.pstmtGetConfigValue.setString(1, key);
51.736 -
51.737 - rs = this.pstmtGetConfigValue.executeQuery();
51.738 - if(rs.next())
51.739 - {
51.740 - return rs.getString(1); // First data on index 1 not 0
51.741 - }
51.742 - else
51.743 - {
51.744 - return null;
51.745 - }
51.746 - }
51.747 - catch(SQLException ex)
51.748 - {
51.749 - restartConnection(ex);
51.750 - return getConfigValue(key);
51.751 - }
51.752 - finally
51.753 - {
51.754 - if(rs != null)
51.755 - {
51.756 - rs.close();
51.757 - restarts = 0; // Clear the restart count after successful request
51.758 - }
51.759 - }
51.760 - }
51.761 -
51.762 - public int getEventsCount(byte type, long start, long end, Group group)
51.763 - throws SQLException
51.764 - {
51.765 - ResultSet rs = null;
51.766 -
51.767 - try
51.768 - {
51.769 - if(group == null)
51.770 - {
51.771 - this.pstmtGetEventsCount0.setInt(1, type);
51.772 - this.pstmtGetEventsCount0.setLong(2, start);
51.773 - this.pstmtGetEventsCount0.setLong(3, end);
51.774 - rs = this.pstmtGetEventsCount0.executeQuery();
51.775 - }
51.776 - else
51.777 - {
51.778 - this.pstmtGetEventsCount1.setInt(1, type);
51.779 - this.pstmtGetEventsCount1.setLong(2, start);
51.780 - this.pstmtGetEventsCount1.setLong(3, end);
51.781 - this.pstmtGetEventsCount1.setLong(4, group.getID());
51.782 - rs = this.pstmtGetEventsCount1.executeQuery();
51.783 - }
51.784 -
51.785 - if(rs.next())
51.786 - {
51.787 - return rs.getInt(1);
51.788 - }
51.789 - else
51.790 - {
51.791 - return -1;
51.792 - }
51.793 - }
51.794 - catch(SQLException ex)
51.795 - {
51.796 - restartConnection(ex);
51.797 - return getEventsCount(type, start, end, group);
51.798 - }
51.799 - finally
51.800 - {
51.801 - if(rs != null)
51.802 - rs.close();
51.803 - }
51.804 - }
51.805 -
51.806 - /**
51.807 - * Reads all Groups from the Database.
51.808 - * @return
51.809 - * @throws java.sql.SQLException
51.810 - */
51.811 - public List<Group> getGroups()
51.812 - throws SQLException
51.813 - {
51.814 - ResultSet rs;
51.815 - List<Group> buffer = new ArrayList<Group>();
51.816 - Statement stmt = null;
51.817 -
51.818 - try
51.819 - {
51.820 - stmt = conn.createStatement();
51.821 - rs = stmt.executeQuery("SELECT * FROM groups ORDER BY name");
51.822 -
51.823 - while(rs.next())
51.824 - {
51.825 - String name = rs.getString("name");
51.826 - long id = rs.getLong("group_id");
51.827 - int flags = rs.getInt("flags");
51.828 -
51.829 - Group group = new Group(name, id, flags);
51.830 - buffer.add(group);
51.831 - }
51.832 -
51.833 - return buffer;
51.834 - }
51.835 - catch(SQLException ex)
51.836 - {
51.837 - restartConnection(ex);
51.838 - return getGroups();
51.839 - }
51.840 - finally
51.841 - {
51.842 - if(stmt != null)
51.843 - stmt.close(); // Implicitely closes ResultSets
51.844 - }
51.845 - }
51.846 -
51.847 - public String getGroupForList(InternetAddress listAddress)
51.848 - throws SQLException
51.849 - {
51.850 - ResultSet rs = null;
51.851 -
51.852 - try
51.853 - {
51.854 - this.pstmtGetGroupForList.setString(1, listAddress.getAddress());
51.855 -
51.856 - rs = this.pstmtGetGroupForList.executeQuery();
51.857 - if (rs.next())
51.858 - {
51.859 - return rs.getString(1);
51.860 - }
51.861 - else
51.862 - {
51.863 - return null;
51.864 - }
51.865 - }
51.866 - catch(SQLException ex)
51.867 - {
51.868 - restartConnection(ex);
51.869 - return getGroupForList(listAddress);
51.870 - }
51.871 - finally
51.872 - {
51.873 - if(rs != null)
51.874 - rs.close();
51.875 - }
51.876 - }
51.877 -
51.878 - /**
51.879 - * Returns the Group that is identified by the name.
51.880 - * @param name
51.881 - * @return
51.882 - * @throws java.sql.SQLException
51.883 - */
51.884 - public Group getGroup(String name)
51.885 - throws SQLException
51.886 - {
51.887 - ResultSet rs = null;
51.888 -
51.889 - try
51.890 - {
51.891 - this.pstmtGetGroup0.setString(1, name);
51.892 - rs = this.pstmtGetGroup0.executeQuery();
51.893 -
51.894 - if (!rs.next())
51.895 - {
51.896 - return null;
51.897 - }
51.898 - else
51.899 - {
51.900 - long id = rs.getLong("group_id");
51.901 - int flags = rs.getInt("flags");
51.902 - return new Group(name, id, flags);
51.903 - }
51.904 - }
51.905 - catch(SQLException ex)
51.906 - {
51.907 - restartConnection(ex);
51.908 - return getGroup(name);
51.909 - }
51.910 - finally
51.911 - {
51.912 - if(rs != null)
51.913 - rs.close();
51.914 - }
51.915 - }
51.916 -
51.917 - public String getListForGroup(String group)
51.918 - throws SQLException
51.919 - {
51.920 - ResultSet rs = null;
51.921 -
51.922 - try
51.923 - {
51.924 - this.pstmtGetListForGroup.setString(1, group);
51.925 - rs = this.pstmtGetListForGroup.executeQuery();
51.926 - if (rs.next())
51.927 - {
51.928 - return rs.getString(1);
51.929 - }
51.930 - else
51.931 - {
51.932 - return null;
51.933 - }
51.934 - }
51.935 - catch(SQLException ex)
51.936 - {
51.937 - restartConnection(ex);
51.938 - return getListForGroup(group);
51.939 - }
51.940 - finally
51.941 - {
51.942 - if(rs != null)
51.943 - rs.close();
51.944 - }
51.945 - }
51.946 -
51.947 - private int getMaxArticleIndex(long groupID)
51.948 - throws SQLException
51.949 - {
51.950 - ResultSet rs = null;
51.951 -
51.952 - try
51.953 - {
51.954 - this.pstmtGetMaxArticleIndex.setLong(1, groupID);
51.955 - rs = this.pstmtGetMaxArticleIndex.executeQuery();
51.956 -
51.957 - int maxIndex = 0;
51.958 - if (rs.next())
51.959 - {
51.960 - maxIndex = rs.getInt(1);
51.961 - }
51.962 -
51.963 - return maxIndex;
51.964 - }
51.965 - catch(SQLException ex)
51.966 - {
51.967 - restartConnection(ex);
51.968 - return getMaxArticleIndex(groupID);
51.969 - }
51.970 - finally
51.971 - {
51.972 - if(rs != null)
51.973 - rs.close();
51.974 - }
51.975 - }
51.976 -
51.977 - private int getMaxArticleID()
51.978 - throws SQLException
51.979 - {
51.980 - ResultSet rs = null;
51.981 -
51.982 - try
51.983 - {
51.984 - rs = this.pstmtGetMaxArticleID.executeQuery();
51.985 -
51.986 - int maxIndex = 0;
51.987 - if (rs.next())
51.988 - {
51.989 - maxIndex = rs.getInt(1);
51.990 - }
51.991 -
51.992 - return maxIndex;
51.993 - }
51.994 - catch(SQLException ex)
51.995 - {
51.996 - restartConnection(ex);
51.997 - return getMaxArticleID();
51.998 - }
51.999 - finally
51.1000 - {
51.1001 - if(rs != null)
51.1002 - rs.close();
51.1003 - }
51.1004 - }
51.1005 -
51.1006 - public int getLastArticleNumber(Group group)
51.1007 - throws SQLException
51.1008 - {
51.1009 - ResultSet rs = null;
51.1010 -
51.1011 - try
51.1012 - {
51.1013 - this.pstmtGetLastArticleNumber.setLong(1, group.getID());
51.1014 - rs = this.pstmtGetLastArticleNumber.executeQuery();
51.1015 - if (rs.next())
51.1016 - {
51.1017 - return rs.getInt(1);
51.1018 - }
51.1019 - else
51.1020 - {
51.1021 - return 0;
51.1022 - }
51.1023 - }
51.1024 - catch(SQLException ex)
51.1025 - {
51.1026 - restartConnection(ex);
51.1027 - return getLastArticleNumber(group);
51.1028 - }
51.1029 - finally
51.1030 - {
51.1031 - if(rs != null)
51.1032 - rs.close();
51.1033 - }
51.1034 - }
51.1035 -
51.1036 - public int getFirstArticleNumber(Group group)
51.1037 - throws SQLException
51.1038 - {
51.1039 - ResultSet rs = null;
51.1040 - try
51.1041 - {
51.1042 - this.pstmtGetFirstArticleNumber.setLong(1, group.getID());
51.1043 - rs = this.pstmtGetFirstArticleNumber.executeQuery();
51.1044 - if(rs.next())
51.1045 - {
51.1046 - return rs.getInt(1);
51.1047 - }
51.1048 - else
51.1049 - {
51.1050 - return 0;
51.1051 - }
51.1052 - }
51.1053 - catch(SQLException ex)
51.1054 - {
51.1055 - restartConnection(ex);
51.1056 - return getFirstArticleNumber(group);
51.1057 - }
51.1058 - finally
51.1059 - {
51.1060 - if(rs != null)
51.1061 - rs.close();
51.1062 - }
51.1063 - }
51.1064 -
51.1065 - /**
51.1066 - * Returns a group name identified by the given id.
51.1067 - * @param id
51.1068 - * @return
51.1069 - * @throws java.sql.SQLException
51.1070 - */
51.1071 - public String getGroup(int id)
51.1072 - throws SQLException
51.1073 - {
51.1074 - ResultSet rs = null;
51.1075 -
51.1076 - try
51.1077 - {
51.1078 - this.pstmtGetGroup1.setInt(1, id);
51.1079 - rs = this.pstmtGetGroup1.executeQuery();
51.1080 -
51.1081 - if (rs.next())
51.1082 - {
51.1083 - return rs.getString(1);
51.1084 - }
51.1085 - else
51.1086 - {
51.1087 - return null;
51.1088 - }
51.1089 - }
51.1090 - catch(SQLException ex)
51.1091 - {
51.1092 - restartConnection(ex);
51.1093 - return getGroup(id);
51.1094 - }
51.1095 - finally
51.1096 - {
51.1097 - if(rs != null)
51.1098 - rs.close();
51.1099 - }
51.1100 - }
51.1101 -
51.1102 - public double getNumberOfEventsPerHour(int key, long gid)
51.1103 - throws SQLException
51.1104 - {
51.1105 - String gidquery = "";
51.1106 - if(gid >= 0)
51.1107 - {
51.1108 - gidquery = " AND group_id = " + gid;
51.1109 - }
51.1110 -
51.1111 - Statement stmt = null;
51.1112 - ResultSet rs = null;
51.1113 -
51.1114 - try
51.1115 - {
51.1116 - stmt = this.conn.createStatement();
51.1117 - rs = stmt.executeQuery("SELECT Count(*) / (Max(event_time) - Min(event_time))" +
51.1118 - " * 1000 * 60 * 60 FROM events WHERE event_key = " + key + gidquery);
51.1119 -
51.1120 - if(rs.next())
51.1121 - {
51.1122 - restarts = 0; // reset error count
51.1123 - return rs.getDouble(1);
51.1124 - }
51.1125 - else
51.1126 - {
51.1127 - return Double.NaN;
51.1128 - }
51.1129 - }
51.1130 - catch(SQLException ex)
51.1131 - {
51.1132 - restartConnection(ex);
51.1133 - return getNumberOfEventsPerHour(key, gid);
51.1134 - }
51.1135 - finally
51.1136 - {
51.1137 - if(stmt != null)
51.1138 - {
51.1139 - stmt.close();
51.1140 - }
51.1141 -
51.1142 - if(rs != null)
51.1143 - {
51.1144 - rs.close();
51.1145 - }
51.1146 - }
51.1147 - }
51.1148 -
51.1149 - public int getPostingsCount(String groupname)
51.1150 - throws SQLException
51.1151 - {
51.1152 - ResultSet rs = null;
51.1153 -
51.1154 - try
51.1155 - {
51.1156 - this.pstmtGetPostingsCount.setString(1, groupname);
51.1157 - rs = this.pstmtGetPostingsCount.executeQuery();
51.1158 - if(rs.next())
51.1159 - {
51.1160 - return rs.getInt(1);
51.1161 - }
51.1162 - else
51.1163 - {
51.1164 - Log.msg("Warning: Count on postings return nothing!", true);
51.1165 - return 0;
51.1166 - }
51.1167 - }
51.1168 - catch(SQLException ex)
51.1169 - {
51.1170 - restartConnection(ex);
51.1171 - return getPostingsCount(groupname);
51.1172 - }
51.1173 - finally
51.1174 - {
51.1175 - if(rs != null)
51.1176 - rs.close();
51.1177 - }
51.1178 - }
51.1179 -
51.1180 - public List<Subscription> getSubscriptions(int feedtype)
51.1181 - throws SQLException
51.1182 - {
51.1183 - ResultSet rs = null;
51.1184 -
51.1185 - try
51.1186 - {
51.1187 - List<Subscription> subs = new ArrayList<Subscription>();
51.1188 - this.pstmtGetSubscriptions.setInt(1, feedtype);
51.1189 - rs = this.pstmtGetSubscriptions.executeQuery();
51.1190 -
51.1191 - while(rs.next())
51.1192 - {
51.1193 - String host = rs.getString("host");
51.1194 - String group = rs.getString("name");
51.1195 - int port = rs.getInt("port");
51.1196 - subs.add(new Subscription(host, port, feedtype, group));
51.1197 - }
51.1198 -
51.1199 - return subs;
51.1200 - }
51.1201 - catch(SQLException ex)
51.1202 - {
51.1203 - restartConnection(ex);
51.1204 - return getSubscriptions(feedtype);
51.1205 - }
51.1206 - finally
51.1207 - {
51.1208 - if(rs != null)
51.1209 - rs.close();
51.1210 - }
51.1211 - }
51.1212 -
51.1213 - /**
51.1214 - * Checks if there is an article with the given messageid in the Database.
51.1215 - * @param name
51.1216 - * @return
51.1217 - * @throws java.sql.SQLException
51.1218 - */
51.1219 - public boolean isArticleExisting(String messageID)
51.1220 - throws SQLException
51.1221 - {
51.1222 - ResultSet rs = null;
51.1223 -
51.1224 - try
51.1225 - {
51.1226 - this.pstmtIsArticleExisting.setString(1, messageID);
51.1227 - rs = this.pstmtIsArticleExisting.executeQuery();
51.1228 - return rs.next() && rs.getInt(1) == 1;
51.1229 - }
51.1230 - catch(SQLException ex)
51.1231 - {
51.1232 - restartConnection(ex);
51.1233 - return isArticleExisting(messageID);
51.1234 - }
51.1235 - finally
51.1236 - {
51.1237 - if(rs != null)
51.1238 - rs.close();
51.1239 - }
51.1240 - }
51.1241 -
51.1242 - /**
51.1243 - * Checks if there is a group with the given name in the Database.
51.1244 - * @param name
51.1245 - * @return
51.1246 - * @throws java.sql.SQLException
51.1247 - */
51.1248 - public boolean isGroupExisting(String name)
51.1249 - throws SQLException
51.1250 - {
51.1251 - ResultSet rs = null;
51.1252 -
51.1253 - try
51.1254 - {
51.1255 - this.pstmtIsGroupExisting.setString(1, name);
51.1256 - rs = this.pstmtIsGroupExisting.executeQuery();
51.1257 - return rs.next();
51.1258 - }
51.1259 - catch(SQLException ex)
51.1260 - {
51.1261 - restartConnection(ex);
51.1262 - return isGroupExisting(name);
51.1263 - }
51.1264 - finally
51.1265 - {
51.1266 - if(rs != null)
51.1267 - rs.close();
51.1268 - }
51.1269 - }
51.1270 -
51.1271 - public void setConfigValue(String key, String value)
51.1272 - throws SQLException
51.1273 - {
51.1274 - try
51.1275 - {
51.1276 - conn.setAutoCommit(false);
51.1277 - this.pstmtSetConfigValue0.setString(1, key);
51.1278 - this.pstmtSetConfigValue0.execute();
51.1279 - this.pstmtSetConfigValue1.setString(1, key);
51.1280 - this.pstmtSetConfigValue1.setString(2, value);
51.1281 - this.pstmtSetConfigValue1.execute();
51.1282 - conn.commit();
51.1283 - conn.setAutoCommit(true);
51.1284 - }
51.1285 - catch(SQLException ex)
51.1286 - {
51.1287 - restartConnection(ex);
51.1288 - setConfigValue(key, value);
51.1289 - }
51.1290 - }
51.1291 -
51.1292 - /**
51.1293 - * Closes the Database connection.
51.1294 - */
51.1295 - public void shutdown()
51.1296 - throws SQLException
51.1297 - {
51.1298 - if(this.conn != null)
51.1299 - {
51.1300 - this.conn.close();
51.1301 - }
51.1302 - }
51.1303 -
51.1304 - private void restartConnection(SQLException cause)
51.1305 - throws SQLException
51.1306 - {
51.1307 - restarts++;
51.1308 - Log.msg(Thread.currentThread()
51.1309 - + ": Database connection was closed (restart " + restarts + ").", false);
51.1310 -
51.1311 - if(restarts >= MAX_RESTARTS)
51.1312 - {
51.1313 - // Delete the current, probably broken Database instance.
51.1314 - // So no one can use the instance any more.
51.1315 - Database.instances.remove(Thread.currentThread());
51.1316 -
51.1317 - // Throw the exception upwards
51.1318 - throw cause;
51.1319 - }
51.1320 -
51.1321 - try
51.1322 - {
51.1323 - Thread.sleep(1500L * restarts);
51.1324 - }
51.1325 - catch(InterruptedException ex)
51.1326 - {
51.1327 - Log.msg("Interrupted: " + ex.getMessage(), false);
51.1328 - }
51.1329 -
51.1330 - // Try to properly close the old database connection
51.1331 - try
51.1332 - {
51.1333 - if(this.conn != null)
51.1334 - {
51.1335 - this.conn.close();
51.1336 - }
51.1337 - }
51.1338 - catch(SQLException ex)
51.1339 - {
51.1340 - Log.msg(ex.getMessage(), true);
51.1341 - }
51.1342 -
51.1343 - try
51.1344 - {
51.1345 - // Try to reinitialize database connection
51.1346 - arise();
51.1347 - }
51.1348 - catch(SQLException ex)
51.1349 - {
51.1350 - Log.msg(ex.getMessage(), true);
51.1351 - restartConnection(ex);
51.1352 - }
51.1353 - }
51.1354 -
51.1355 -}
52.1 --- a/org/sonews/daemon/storage/Group.java Wed Jul 01 10:48:22 2009 +0200
52.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
52.3 @@ -1,186 +0,0 @@
52.4 -/*
52.5 - * SONEWS News Server
52.6 - * see AUTHORS for the list of contributors
52.7 - *
52.8 - * This program is free software: you can redistribute it and/or modify
52.9 - * it under the terms of the GNU General Public License as published by
52.10 - * the Free Software Foundation, either version 3 of the License, or
52.11 - * (at your option) any later version.
52.12 - *
52.13 - * This program is distributed in the hope that it will be useful,
52.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
52.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
52.16 - * GNU General Public License for more details.
52.17 - *
52.18 - * You should have received a copy of the GNU General Public License
52.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
52.20 - */
52.21 -
52.22 -package org.sonews.daemon.storage;
52.23 -
52.24 -import java.sql.SQLException;
52.25 -import java.util.List;
52.26 -import org.sonews.util.Log;
52.27 -import org.sonews.util.Pair;
52.28 -
52.29 -/**
52.30 - * Represents a logical Group within this newsserver.
52.31 - * @author Christian Lins
52.32 - * @since sonews/0.5.0
52.33 - */
52.34 -public class Group
52.35 -{
52.36 -
52.37 - /**
52.38 - * If this flag is set the Group is no real newsgroup but a mailing list
52.39 - * mirror. In that case every posting and receiving mails must go through
52.40 - * the mailing list gateway.
52.41 - */
52.42 - public static final int MAILINGLIST = 0x1;
52.43 -
52.44 - /**
52.45 - * If this flag is set the Group is marked as readonly and the posting
52.46 - * is prohibited. This can be useful for groups that are synced only in
52.47 - * one direction.
52.48 - */
52.49 - public static final int READONLY = 0x2;
52.50 -
52.51 - /**
52.52 - * If this flag is set the Group is marked as deleted and must not occur
52.53 - * in any output. The deletion is done lazily by a low priority daemon.
52.54 - */
52.55 - public static final int DELETED = 0x128;
52.56 -
52.57 - private long id = 0;
52.58 - private int flags = -1;
52.59 - private String name = null;
52.60 -
52.61 - /**
52.62 - * Returns a Group identified by its full name.
52.63 - * @param name
52.64 - * @return
52.65 - */
52.66 - public static Group getByName(final String name)
52.67 - {
52.68 - try
52.69 - {
52.70 - return Database.getInstance().getGroup(name);
52.71 - }
52.72 - catch(SQLException ex)
52.73 - {
52.74 - ex.printStackTrace();
52.75 - return null;
52.76 - }
52.77 - }
52.78 -
52.79 - /**
52.80 - * @return List of all groups this server handles.
52.81 - */
52.82 - public static List<Group> getAll()
52.83 - {
52.84 - try
52.85 - {
52.86 - return Database.getInstance().getGroups();
52.87 - }
52.88 - catch(SQLException ex)
52.89 - {
52.90 - Log.msg(ex.getMessage(), false);
52.91 - return null;
52.92 - }
52.93 - }
52.94 -
52.95 - /**
52.96 - * Private constructor.
52.97 - * @param name
52.98 - * @param id
52.99 - */
52.100 - Group(final String name, final long id, final int flags)
52.101 - {
52.102 - this.id = id;
52.103 - this.flags = flags;
52.104 - this.name = name;
52.105 - }
52.106 -
52.107 - @Override
52.108 - public boolean equals(Object obj)
52.109 - {
52.110 - if(obj instanceof Group)
52.111 - {
52.112 - return ((Group)obj).id == this.id;
52.113 - }
52.114 - else
52.115 - {
52.116 - return false;
52.117 - }
52.118 - }
52.119 -
52.120 - public List<Pair<Long, ArticleHead>> getArticleHeads(final int first, final int last)
52.121 - throws SQLException
52.122 - {
52.123 - return Database.getInstance().getArticleHeads(this, first, last);
52.124 - }
52.125 -
52.126 - public List<Long> getArticleNumbers()
52.127 - throws SQLException
52.128 - {
52.129 - return Database.getInstance().getArticleNumbers(id);
52.130 - }
52.131 -
52.132 - public int getFirstArticleNumber()
52.133 - throws SQLException
52.134 - {
52.135 - return Database.getInstance().getFirstArticleNumber(this);
52.136 - }
52.137 -
52.138 - /**
52.139 - * Returns the group id.
52.140 - */
52.141 - public long getID()
52.142 - {
52.143 - assert id > 0;
52.144 -
52.145 - return id;
52.146 - }
52.147 -
52.148 - public boolean isMailingList()
52.149 - {
52.150 - return (this.flags & MAILINGLIST) != 0;
52.151 - }
52.152 -
52.153 - public int getLastArticleNumber()
52.154 - throws SQLException
52.155 - {
52.156 - return Database.getInstance().getLastArticleNumber(this);
52.157 - }
52.158 -
52.159 - public String getName()
52.160 - {
52.161 - return name;
52.162 - }
52.163 -
52.164 - /**
52.165 - * Performs this.flags |= flag to set a specified flag and updates the data
52.166 - * in the Database.
52.167 - * @param flag
52.168 - */
52.169 - public void setFlag(final int flag)
52.170 - {
52.171 - this.flags |= flag;
52.172 - }
52.173 -
52.174 - public void setName(final String name)
52.175 - {
52.176 - this.name = name;
52.177 - }
52.178 -
52.179 - /**
52.180 - * @return Number of posted articles in this group.
52.181 - * @throws java.sql.SQLException
52.182 - */
52.183 - public int getPostingsCount()
52.184 - throws SQLException
52.185 - {
52.186 - return Database.getInstance().getPostingsCount(this.name);
52.187 - }
52.188 -
52.189 -}
53.1 --- a/org/sonews/daemon/storage/Headers.java Wed Jul 01 10:48:22 2009 +0200
53.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
53.3 @@ -1,51 +0,0 @@
53.4 -/*
53.5 - * SONEWS News Server
53.6 - * see AUTHORS for the list of contributors
53.7 - *
53.8 - * This program is free software: you can redistribute it and/or modify
53.9 - * it under the terms of the GNU General Public License as published by
53.10 - * the Free Software Foundation, either version 3 of the License, or
53.11 - * (at your option) any later version.
53.12 - *
53.13 - * This program is distributed in the hope that it will be useful,
53.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
53.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
53.16 - * GNU General Public License for more details.
53.17 - *
53.18 - * You should have received a copy of the GNU General Public License
53.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
53.20 - */
53.21 -
53.22 -package org.sonews.daemon.storage;
53.23 -
53.24 -/**
53.25 - * Contains header constants. These header keys are no way complete but all
53.26 - * headers that are relevant for sonews.
53.27 - * @author Christian Lins
53.28 - * @since sonews/0.5.0
53.29 - */
53.30 -public final class Headers
53.31 -{
53.32 -
53.33 - public static final String BYTES = "bytes";
53.34 - public static final String CONTENT_TYPE = "content-type";
53.35 - public static final String CONTROL = "control";
53.36 - public static final String DATE = "date";
53.37 - public static final String FROM = "from";
53.38 - public static final String LINES = "lines";
53.39 - public static final String MESSAGE_ID = "message-id";
53.40 - public static final String NEWSGROUPS = "newsgroups";
53.41 - public static final String NNTP_POSTING_DATE = "nntp-posting-date";
53.42 - public static final String NNTP_POSTING_HOST = "nntp-posting-host";
53.43 - public static final String PATH = "path";
53.44 - public static final String REFERENCES = "references";
53.45 - public static final String SUBJECT = "subject";
53.46 - public static final String SUPERSEDES = "subersedes";
53.47 - public static final String X_COMPLAINTS_TO = "x-complaints-to";
53.48 - public static final String X_TRACE = "x-trace";
53.49 - public static final String XREF = "xref";
53.50 -
53.51 - private Headers()
53.52 - {}
53.53 -
53.54 -}
54.1 --- a/org/sonews/daemon/storage/package.html Wed Jul 01 10:48:22 2009 +0200
54.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
54.3 @@ -1,2 +0,0 @@
54.4 -Contains classes of the storage backend and the Group and Article
54.5 -abstraction.
54.6 \ No newline at end of file
55.1 --- a/org/sonews/feed/FeedManager.java Wed Jul 01 10:48:22 2009 +0200
55.2 +++ b/org/sonews/feed/FeedManager.java Wed Jul 22 14:04:05 2009 +0200
55.3 @@ -18,10 +18,10 @@
55.4
55.5 package org.sonews.feed;
55.6
55.7 -import java.sql.SQLException;
55.8 import java.util.List;
55.9 -import org.sonews.daemon.storage.Article;
55.10 -import org.sonews.daemon.storage.Database;
55.11 +import org.sonews.storage.Article;
55.12 +import org.sonews.storage.StorageBackendException;
55.13 +import org.sonews.storage.StorageManager;
55.14
55.15 /**
55.16 * Controlls push and pull feeder.
55.17 @@ -42,9 +42,9 @@
55.18 * PullFeeder or PushFeeder.
55.19 */
55.20 public static synchronized void startFeeding()
55.21 - throws SQLException
55.22 + throws StorageBackendException
55.23 {
55.24 - List<Subscription> subsPull = Database.getInstance()
55.25 + List<Subscription> subsPull = StorageManager.current()
55.26 .getSubscriptions(TYPE_PULL);
55.27 for(Subscription sub : subsPull)
55.28 {
55.29 @@ -52,7 +52,7 @@
55.30 }
55.31 pullFeeder.start();
55.32
55.33 - List<Subscription> subsPush = Database.getInstance()
55.34 + List<Subscription> subsPush = StorageManager.current()
55.35 .getSubscriptions(TYPE_PUSH);
55.36 for(Subscription sub : subsPush)
55.37 {
56.1 --- a/org/sonews/feed/PullFeeder.java Wed Jul 01 10:48:22 2009 +0200
56.2 +++ b/org/sonews/feed/PullFeeder.java Wed Jul 22 14:04:05 2009 +0200
56.3 @@ -25,14 +25,14 @@
56.4 import java.net.Socket;
56.5 import java.net.SocketException;
56.6 import java.net.UnknownHostException;
56.7 -import java.sql.SQLException;
56.8 import java.util.ArrayList;
56.9 import java.util.HashMap;
56.10 import java.util.List;
56.11 import java.util.Map;
56.12 -import org.sonews.daemon.Config;
56.13 +import org.sonews.config.Config;
56.14 import org.sonews.util.Log;
56.15 -import org.sonews.daemon.storage.Database;
56.16 +import org.sonews.storage.StorageBackendException;
56.17 +import org.sonews.storage.StorageManager;
56.18 import org.sonews.util.Stats;
56.19 import org.sonews.util.io.ArticleReader;
56.20 import org.sonews.util.io.ArticleWriter;
56.21 @@ -154,7 +154,7 @@
56.22 while(isRunning())
56.23 {
56.24 int pullInterval = 1000 *
56.25 - Config.getInstance().get(Config.FEED_PULLINTERVAL, 3600);
56.26 + Config.inst().get(Config.FEED_PULLINTERVAL, 3600);
56.27 String host = "localhost";
56.28 int port = 119;
56.29
56.30 @@ -189,36 +189,34 @@
56.31
56.32 for(String messageID : messageIDs)
56.33 {
56.34 - if(Database.getInstance().isArticleExisting(messageID))
56.35 + if(!StorageManager.current().isArticleExisting(messageID))
56.36 {
56.37 - continue;
56.38 - }
56.39 -
56.40 - try
56.41 - {
56.42 - // Post the message via common socket connection
56.43 - ArticleReader aread =
56.44 - new ArticleReader(sub.getHost(), sub.getPort(), messageID);
56.45 - byte[] abuf = aread.getArticleData();
56.46 - if (abuf == null)
56.47 + try
56.48 {
56.49 - Log.msg("Could not feed " + messageID + " from " + sub.getHost(), true);
56.50 + // Post the message via common socket connection
56.51 + ArticleReader aread =
56.52 + new ArticleReader(sub.getHost(), sub.getPort(), messageID);
56.53 + byte[] abuf = aread.getArticleData();
56.54 + if (abuf == null)
56.55 + {
56.56 + Log.msg("Could not feed " + messageID + " from " + sub.getHost(), true);
56.57 + }
56.58 + else
56.59 + {
56.60 + Log.msg("Feeding " + messageID, true);
56.61 + ArticleWriter awrite = new ArticleWriter(
56.62 + "localhost", Config.inst().get(Config.PORT, 119));
56.63 + awrite.writeArticle(abuf);
56.64 + awrite.close();
56.65 + }
56.66 + Stats.getInstance().mailFeeded(sub.getGroup());
56.67 }
56.68 - else
56.69 + catch(IOException ex)
56.70 {
56.71 - Log.msg("Feeding " + messageID, true);
56.72 - ArticleWriter awrite = new ArticleWriter(
56.73 - "localhost", Config.getInstance().get(Config.PORT, 119));
56.74 - awrite.writeArticle(abuf);
56.75 - awrite.close();
56.76 + // There may be a temporary network failure
56.77 + ex.printStackTrace();
56.78 + Log.msg("Skipping mail " + messageID + " due to exception.", false);
56.79 }
56.80 - Stats.getInstance().mailFeeded(sub.getGroup());
56.81 - }
56.82 - catch(IOException ex)
56.83 - {
56.84 - // There may be a temporary network failure
56.85 - ex.printStackTrace();
56.86 - Log.msg("Skipping mail " + messageID + " due to exception.", false);
56.87 }
56.88 } // for(;;)
56.89 this.highMarks.put(sub, newMark);
56.90 @@ -226,7 +224,7 @@
56.91
56.92 disconnect();
56.93 }
56.94 - catch(SQLException ex)
56.95 + catch(StorageBackendException ex)
56.96 {
56.97 ex.printStackTrace();
56.98 }
57.1 --- a/org/sonews/feed/PushFeeder.java Wed Jul 01 10:48:22 2009 +0200
57.2 +++ b/org/sonews/feed/PushFeeder.java Wed Jul 22 14:04:05 2009 +0200
57.3 @@ -20,8 +20,8 @@
57.4
57.5 import java.io.IOException;
57.6 import java.util.concurrent.ConcurrentLinkedQueue;
57.7 -import org.sonews.daemon.storage.Article;
57.8 -import org.sonews.daemon.storage.Headers;
57.9 +import org.sonews.storage.Article;
57.10 +import org.sonews.storage.Headers;
57.11 import org.sonews.util.Log;
57.12 import org.sonews.util.io.ArticleWriter;
57.13
57.14 @@ -90,7 +90,7 @@
57.15 }
57.16 catch(InterruptedException ex)
57.17 {
57.18 - Log.msg("PushFeeder interrupted.", true);
57.19 + Log.msg("PushFeeder interrupted: " + ex, true);
57.20 }
57.21 }
57.22 }
58.1 --- a/org/sonews/mlgw/Dispatcher.java Wed Jul 01 10:48:22 2009 +0200
58.2 +++ b/org/sonews/mlgw/Dispatcher.java Wed Jul 22 14:04:05 2009 +0200
58.3 @@ -19,24 +19,19 @@
58.4 package org.sonews.mlgw;
58.5
58.6 import java.io.IOException;
58.7 -import org.sonews.daemon.Config;
58.8 -import org.sonews.daemon.storage.Article;
58.9 -import org.sonews.util.io.ArticleInputStream;
58.10 -import org.sonews.daemon.storage.Database;
58.11 -import java.sql.SQLException;
58.12 import java.util.ArrayList;
58.13 import java.util.List;
58.14 -import java.util.Properties;
58.15 import javax.mail.Address;
58.16 import javax.mail.Authenticator;
58.17 import javax.mail.Message;
58.18 import javax.mail.MessagingException;
58.19 import javax.mail.PasswordAuthentication;
58.20 -import javax.mail.Session;
58.21 -import javax.mail.Transport;
58.22 import javax.mail.internet.InternetAddress;
58.23 -import javax.mail.internet.MimeMessage;
58.24 -import org.sonews.daemon.storage.Headers;
58.25 +import org.sonews.config.Config;
58.26 +import org.sonews.storage.Article;
58.27 +import org.sonews.storage.Headers;
58.28 +import org.sonews.storage.StorageBackendException;
58.29 +import org.sonews.storage.StorageManager;
58.30 import org.sonews.util.Log;
58.31 import org.sonews.util.Stats;
58.32
58.33 @@ -55,9 +50,9 @@
58.34 public PasswordAuthentication getPasswordAuthentication()
58.35 {
58.36 final String username =
58.37 - Config.getInstance().get(Config.MLSEND_USER, "user");
58.38 + Config.inst().get(Config.MLSEND_USER, "user");
58.39 final String password =
58.40 - Config.getInstance().get(Config.MLSEND_PASSWORD, "mysecret");
58.41 + Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
58.42
58.43 return new PasswordAuthentication(username, password);
58.44 }
58.45 @@ -76,48 +71,71 @@
58.46 Address[] to = msg.getAllRecipients(); // includes TO/CC/BCC
58.47 if(to == null || to.length <= 0)
58.48 {
58.49 - Log.msg("Skipping message because no receipient!", true);
58.50 + to = msg.getReplyTo();
58.51 + }
58.52 +
58.53 + if(to == null || to.length <= 0)
58.54 + {
58.55 + Log.msg("Skipping message because no recipient!", false);
58.56 return false;
58.57 }
58.58 else
58.59 {
58.60 - boolean posted = false;
58.61 - for(Address toa : to) // Address can have '<' '>' around
58.62 + boolean posted = false;
58.63 + List<String> newsgroups = new ArrayList<String>();
58.64 +
58.65 + for (Address toa : to) // Address can have '<' '>' around
58.66 {
58.67 - if(!(toa instanceof InternetAddress))
58.68 + if (toa instanceof InternetAddress)
58.69 {
58.70 - continue;
58.71 + List<String> groups = StorageManager.current()
58.72 + .getGroupsForList((InternetAddress)toa);
58.73 + newsgroups.addAll(groups);
58.74 }
58.75 - String group = Database.getInstance()
58.76 - .getGroupForList((InternetAddress)toa);
58.77 - if(group != null)
58.78 + }
58.79 +
58.80 + if (newsgroups.size() > 0)
58.81 + {
58.82 + StringBuilder groups = new StringBuilder();
58.83 + for(int n = 0; n < newsgroups.size(); n++)
58.84 {
58.85 - Log.msg("Posting to group " + group, true);
58.86 + groups.append(newsgroups.get(n));
58.87 + if(n + 1 != newsgroups.size())
58.88 + {
58.89 + groups.append(',');
58.90 + }
58.91 + }
58.92 + Log.msg("Posting to group " + groups.toString(), true);
58.93
58.94 - // Create new Article object
58.95 - Article article = new Article(msg);
58.96 - article.setGroup(group);
58.97 -
58.98 - // Write article to database
58.99 - if(!Database.getInstance().isArticleExisting(article.getMessageID()))
58.100 - {
58.101 - Database.getInstance().addArticle(article);
58.102 - Stats.getInstance().mailGatewayed(
58.103 - article.getHeader(Headers.NEWSGROUPS)[0]);
58.104 - }
58.105 - else
58.106 - {
58.107 - Log.msg("Article " + article.getMessageID() + " already existing.", true);
58.108 - // TODO: It may be possible that a ML mail is posted to several
58.109 - // ML addresses...
58.110 - }
58.111 - posted = true;
58.112 + // Create new Article object
58.113 + Article article = new Article(msg);
58.114 + article.setGroup(groups.toString());
58.115 + article.removeHeader(Headers.REPLY_TO);
58.116 + article.removeHeader(Headers.TO);
58.117 +
58.118 + // Write article to database
58.119 + if(!StorageManager.current().isArticleExisting(article.getMessageID()))
58.120 + {
58.121 + StorageManager.current().addArticle(article);
58.122 + Stats.getInstance().mailGatewayed(
58.123 + article.getHeader(Headers.NEWSGROUPS)[0]);
58.124 }
58.125 else
58.126 {
58.127 - Log.msg("No group for " + toa, true);
58.128 + Log.msg("Article " + article.getMessageID() + " already existing.", true);
58.129 }
58.130 - } // end for
58.131 + posted = true;
58.132 + }
58.133 + else
58.134 + {
58.135 + StringBuilder buf = new StringBuilder();
58.136 + for(Address toa : to)
58.137 + {
58.138 + buf.append(' ');
58.139 + buf.append(toa.toString());
58.140 + }
58.141 + Log.msg("No group for" + buf.toString(), false);
58.142 + }
58.143 return posted;
58.144 }
58.145 }
58.146 @@ -132,7 +150,7 @@
58.147 * Mails a message received through NNTP to the appropriate mailing list.
58.148 */
58.149 public static void toList(Article article)
58.150 - throws IOException, MessagingException, SQLException
58.151 + throws IOException, MessagingException, StorageBackendException
58.152 {
58.153 // Get mailing lists for the group of this article
58.154 List<String> listAddresses = new ArrayList<String>();
58.155 @@ -140,7 +158,7 @@
58.156
58.157 for(String groupname : groupnames)
58.158 {
58.159 - String listAddress = Database.getInstance().getListForGroup(groupname);
58.160 + String listAddress = StorageManager.current().getListForGroup(groupname);
58.161 if(listAddress != null)
58.162 {
58.163 listAddresses.add(listAddress);
58.164 @@ -150,53 +168,34 @@
58.165 for(String listAddress : listAddresses)
58.166 {
58.167 // Compose message and send it via given SMTP-Host
58.168 - String smtpHost = Config.getInstance().get(Config.MLSEND_HOST, "localhost");
58.169 - int smtpPort = Config.getInstance().get(Config.MLSEND_PORT, 25);
58.170 - String smtpUser = Config.getInstance().get(Config.MLSEND_USER, "user");
58.171 - String smtpPw = Config.getInstance().get(Config.MLSEND_PASSWORD, "mysecret");
58.172 + String smtpHost = Config.inst().get(Config.MLSEND_HOST, "localhost");
58.173 + int smtpPort = Config.inst().get(Config.MLSEND_PORT, 25);
58.174 + String smtpUser = Config.inst().get(Config.MLSEND_USER, "user");
58.175 + String smtpPw = Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
58.176 + String smtpFrom = Config.inst().get(
58.177 + Config.MLSEND_ADDRESS, article.getHeader(Headers.FROM)[0]);
58.178
58.179 - Properties props = System.getProperties();
58.180 - props.put("mail.smtp.localhost",
58.181 - Config.getInstance().get(Config.HOSTNAME, "localhost"));
58.182 - props.put("mail.smtp.from", // Used for MAIL FROM command
58.183 - Config.getInstance().get(
58.184 - Config.MLSEND_ADDRESS, article.getHeader(Headers.FROM)[0]));
58.185 - props.put("mail.smtp.host", smtpHost);
58.186 - props.put("mail.smtp.port", smtpPort);
58.187 - props.put("mail.smtp.auth", "true");
58.188 + // TODO: Make Article cloneable()
58.189 + String group = article.getHeader(Headers.NEWSGROUPS)[0];
58.190 + article.getMessageID(); // Make sure an ID is existing
58.191 + article.removeHeader(Headers.NEWSGROUPS);
58.192 + article.removeHeader(Headers.PATH);
58.193 + article.removeHeader(Headers.LINES);
58.194 + article.removeHeader(Headers.BYTES);
58.195
58.196 - Address[] address = new Address[1];
58.197 - address[0] = new InternetAddress(listAddress);
58.198 + article.setHeader("To", listAddress);
58.199 + article.setHeader("Reply-To", listAddress);
58.200
58.201 - ArticleInputStream in = new ArticleInputStream(article);
58.202 - Session session = Session.getDefaultInstance(props, new PasswordAuthenticator());
58.203 - MimeMessage msg = new MimeMessage(session, in);
58.204 - msg.setRecipient(Message.RecipientType.TO, address[0]);
58.205 - msg.setReplyTo(address);
58.206 - msg.removeHeader(Headers.NEWSGROUPS);
58.207 - msg.removeHeader(Headers.PATH);
58.208 - msg.removeHeader(Headers.LINES);
58.209 - msg.removeHeader(Headers.BYTES);
58.210 -
58.211 - if(Config.getInstance().get(Config.MLSEND_RW_SENDER, false))
58.212 + if(Config.inst().get(Config.MLSEND_RW_SENDER, false))
58.213 {
58.214 - rewriteSenderAddress(msg); // Set the SENDER address
58.215 + rewriteSenderAddress(article); // Set the SENDER address
58.216 }
58.217 -
58.218 - if(Config.getInstance().get(Config.MLSEND_RW_FROM, false))
58.219 - {
58.220 - rewriteFromAddress(msg); // Set the FROM address
58.221 - }
58.222 -
58.223 - msg.saveChanges();
58.224
58.225 - // Send the mail
58.226 - Transport transport = session.getTransport("smtp");
58.227 - transport.connect(smtpHost, smtpPort, smtpUser, smtpPw);
58.228 - transport.sendMessage(msg, msg.getAllRecipients());
58.229 - transport.close();
58.230 + SMTPTransport smtpTransport = new SMTPTransport(smtpHost, smtpPort);
58.231 + smtpTransport.send(article, smtpFrom, listAddress);
58.232 + smtpTransport.close();
58.233
58.234 - Stats.getInstance().mailGatewayed(article.getHeader(Headers.NEWSGROUPS)[0]);
58.235 + Stats.getInstance().mailGatewayed(group);
58.236 Log.msg("MLGateway: Mail " + article.getHeader("Subject")[0]
58.237 + " was delivered to " + listAddress + ".", true);
58.238 }
58.239 @@ -208,14 +207,14 @@
58.240 * @param msg
58.241 * @throws javax.mail.MessagingException
58.242 */
58.243 - private static void rewriteSenderAddress(MimeMessage msg)
58.244 + private static void rewriteSenderAddress(Article msg)
58.245 throws MessagingException
58.246 {
58.247 - String mlAddress = Config.getInstance().get(Config.MLSEND_ADDRESS, null);
58.248 + String mlAddress = Config.inst().get(Config.MLSEND_ADDRESS, null);
58.249
58.250 if(mlAddress != null)
58.251 {
58.252 - msg.setSender(new InternetAddress(mlAddress));
58.253 + msg.setHeader(Headers.SENDER, mlAddress);
58.254 }
58.255 else
58.256 {
58.257 @@ -223,29 +222,4 @@
58.258 }
58.259 }
58.260
58.261 - /**
58.262 - * Sets the FROM header of the given MimeMessage. This might be necessary
58.263 - * for moderated groups that does not allow the "normal" FROM sender.
58.264 - * @param msg
58.265 - * @throws javax.mail.MessagingException
58.266 - */
58.267 - private static void rewriteFromAddress(MimeMessage msg)
58.268 - throws MessagingException
58.269 - {
58.270 - Address[] froms = msg.getFrom();
58.271 - String mlAddress = Config.getInstance().get(Config.MLSEND_ADDRESS, null);
58.272 -
58.273 - if(froms.length > 0 && froms[0] instanceof InternetAddress
58.274 - && mlAddress != null)
58.275 - {
58.276 - InternetAddress from = (InternetAddress)froms[0];
58.277 - from.setAddress(mlAddress);
58.278 - msg.setFrom(from);
58.279 - }
58.280 - else
58.281 - {
58.282 - throw new MessagingException("Cannot rewrite FROM header!");
58.283 - }
58.284 - }
58.285 -
58.286 }
59.1 --- a/org/sonews/mlgw/MailPoller.java Wed Jul 01 10:48:22 2009 +0200
59.2 +++ b/org/sonews/mlgw/MailPoller.java Wed Jul 22 14:04:05 2009 +0200
59.3 @@ -19,6 +19,7 @@
59.4 package org.sonews.mlgw;
59.5
59.6 import java.util.Properties;
59.7 +import javax.mail.Address;
59.8 import javax.mail.AuthenticationFailedException;
59.9 import javax.mail.Authenticator;
59.10 import javax.mail.Flags.Flag;
59.11 @@ -29,7 +30,7 @@
59.12 import javax.mail.PasswordAuthentication;
59.13 import javax.mail.Session;
59.14 import javax.mail.Store;
59.15 -import org.sonews.daemon.Config;
59.16 +import org.sonews.config.Config;
59.17 import org.sonews.daemon.AbstractDaemon;
59.18 import org.sonews.util.Log;
59.19 import org.sonews.util.Stats;
59.20 @@ -49,9 +50,9 @@
59.21 public PasswordAuthentication getPasswordAuthentication()
59.22 {
59.23 final String username =
59.24 - Config.getInstance().get(Config.MLPOLL_USER, "user");
59.25 + Config.inst().get(Config.MLPOLL_USER, "user");
59.26 final String password =
59.27 - Config.getInstance().get(Config.MLPOLL_PASSWORD, "mysecret");
59.28 + Config.inst().get(Config.MLPOLL_PASSWORD, "mysecret");
59.29
59.30 return new PasswordAuthentication(username, password);
59.31 }
59.32 @@ -72,11 +73,11 @@
59.33 Thread.sleep(60000 * (errors + 1)); // one minute * errors
59.34
59.35 final String host =
59.36 - Config.getInstance().get(Config.MLPOLL_HOST, "samplehost");
59.37 + Config.inst().get(Config.MLPOLL_HOST, "samplehost");
59.38 final String username =
59.39 - Config.getInstance().get(Config.MLPOLL_USER, "user");
59.40 + Config.inst().get(Config.MLPOLL_USER, "user");
59.41 final String password =
59.42 - Config.getInstance().get(Config.MLPOLL_PASSWORD, "mysecret");
59.43 + Config.inst().get(Config.MLPOLL_PASSWORD, "mysecret");
59.44
59.45 Stats.getInstance().mlgwRunStart();
59.46
59.47 @@ -101,10 +102,8 @@
59.48 // Dispatch messages and delete it afterwards on the inbox
59.49 for(Message message : messages)
59.50 {
59.51 - String subject = message.getSubject();
59.52 - System.out.println("MLGateway: message with subject \"" + subject + "\" received.");
59.53 if(Dispatcher.toGroup(message)
59.54 - || Config.getInstance().get(Config.MLPOLL_DELETEUNKNOWN, false))
59.55 + || Config.inst().get(Config.MLPOLL_DELETEUNKNOWN, false))
59.56 {
59.57 // Delete the message
59.58 message.setFlag(Flag.DELETED, true);
59.59 @@ -132,7 +131,7 @@
59.60 }
59.61 catch(InterruptedException ex)
59.62 {
59.63 - System.out.println("sonews: " + this + " returns.");
59.64 + System.out.println("sonews: " + this + " returns: " + ex);
59.65 return;
59.66 }
59.67 catch(MessagingException ex)
60.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
60.2 +++ b/org/sonews/mlgw/SMTPTransport.java Wed Jul 22 14:04:05 2009 +0200
60.3 @@ -0,0 +1,134 @@
60.4 +/*
60.5 + * SONEWS News Server
60.6 + * see AUTHORS for the list of contributors
60.7 + *
60.8 + * This program is free software: you can redistribute it and/or modify
60.9 + * it under the terms of the GNU General Public License as published by
60.10 + * the Free Software Foundation, either version 3 of the License, or
60.11 + * (at your option) any later version.
60.12 + *
60.13 + * This program is distributed in the hope that it will be useful,
60.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
60.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
60.16 + * GNU General Public License for more details.
60.17 + *
60.18 + * You should have received a copy of the GNU General Public License
60.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
60.20 + */
60.21 +
60.22 +package org.sonews.mlgw;
60.23 +
60.24 +import java.io.BufferedOutputStream;
60.25 +import java.io.BufferedReader;
60.26 +import java.io.FileOutputStream;
60.27 +import java.io.IOException;
60.28 +import java.io.InputStreamReader;
60.29 +import java.io.PrintWriter;
60.30 +import java.net.Socket;
60.31 +import java.net.UnknownHostException;
60.32 +import org.sonews.config.Config;
60.33 +import org.sonews.storage.Article;
60.34 +import org.sonews.util.io.ArticleInputStream;
60.35 +
60.36 +/**
60.37 + * Connects to a SMTP server and sends a given Article to it.
60.38 + * @author Christian Lins
60.39 + */
60.40 +class SMTPTransport
60.41 +{
60.42 +
60.43 + protected BufferedReader in;
60.44 + protected PrintWriter out;
60.45 + protected Socket socket;
60.46 +
60.47 + public SMTPTransport(String host, int port)
60.48 + throws IOException, UnknownHostException
60.49 + {
60.50 + socket = new Socket(host, port);
60.51 + this.in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
60.52 + this.out = new PrintWriter(socket.getOutputStream());
60.53 +
60.54 + // Read helo from server
60.55 + String line = this.in.readLine();
60.56 + if(line == null || !line.startsWith("220 "))
60.57 + {
60.58 + throw new IOException("Invalid helo from server: " + line);
60.59 + }
60.60 +
60.61 + // Send HELO to server
60.62 + this.out.println("HELO " + Config.inst().get(Config.HOSTNAME, "localhost"));
60.63 + this.out.flush();
60.64 + line = this.in.readLine();
60.65 + if(line == null || !line.startsWith("250 "))
60.66 + {
60.67 + throw new IOException("Unexpected reply: " + line);
60.68 + }
60.69 + }
60.70 +
60.71 + public SMTPTransport(String host)
60.72 + throws IOException
60.73 + {
60.74 + this(host, 25);
60.75 + }
60.76 +
60.77 + public void close()
60.78 + throws IOException
60.79 + {
60.80 + this.out.println("QUIT");
60.81 + this.out.flush();
60.82 + this.in.readLine();
60.83 +
60.84 + this.socket.close();
60.85 + }
60.86 +
60.87 + public void send(Article article, String mailFrom, String rcptTo)
60.88 + throws IOException
60.89 + {
60.90 + this.out.println("MAIL FROM: " + mailFrom);
60.91 + this.out.flush();
60.92 + String line = this.in.readLine();
60.93 + if(line == null || !line.startsWith("250 "))
60.94 + {
60.95 + throw new IOException("Unexpected reply: " + line);
60.96 + }
60.97 +
60.98 + this.out.println("RCPT TO: " + rcptTo);
60.99 + this.out.flush();
60.100 + line = this.in.readLine();
60.101 + if(line == null || !line.startsWith("250 "))
60.102 + {
60.103 + throw new IOException("Unexpected reply: " + line);
60.104 + }
60.105 +
60.106 + this.out.println("DATA");
60.107 + this.out.flush();
60.108 + line = this.in.readLine();
60.109 + if(line == null || !line.startsWith("354 "))
60.110 + {
60.111 + throw new IOException("Unexpected reply: " + line);
60.112 + }
60.113 +
60.114 + ArticleInputStream artStream = new ArticleInputStream(article);
60.115 + BufferedOutputStream outStream = new BufferedOutputStream(socket.getOutputStream());
60.116 + FileOutputStream fileStream = new FileOutputStream("smtp.dump");
60.117 + for(int b = artStream.read(); b >= 0; b = artStream.read())
60.118 + {
60.119 + outStream.write(b);
60.120 + fileStream.write(b);
60.121 + }
60.122 +
60.123 + // Flush the binary stream; important because otherwise the output
60.124 + // will be mixed with the PrintWriter.
60.125 + outStream.flush();
60.126 + fileStream.flush();
60.127 + fileStream.close();
60.128 + this.out.print("\r\n.\r\n");
60.129 + this.out.flush();
60.130 + line = this.in.readLine();
60.131 + if(line == null || !line.startsWith("250 "))
60.132 + {
60.133 + throw new IOException("Unexpected reply: " + line);
60.134 + }
60.135 + }
60.136 +
60.137 +}
61.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
61.2 +++ b/org/sonews/storage/AggregatedGroup.java Wed Jul 22 14:04:05 2009 +0200
61.3 @@ -0,0 +1,260 @@
61.4 +/*
61.5 + * SONEWS News Server
61.6 + * see AUTHORS for the list of contributors
61.7 + *
61.8 + * This program is free software: you can redistribute it and/or modify
61.9 + * it under the terms of the GNU General Public License as published by
61.10 + * the Free Software Foundation, either version 3 of the License, or
61.11 + * (at your option) any later version.
61.12 + *
61.13 + * This program is distributed in the hope that it will be useful,
61.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
61.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
61.16 + * GNU General Public License for more details.
61.17 + *
61.18 + * You should have received a copy of the GNU General Public License
61.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
61.20 + */
61.21 +
61.22 +package org.sonews.storage;
61.23 +
61.24 +import java.util.ArrayList;
61.25 +import java.util.Collections;
61.26 +import java.util.List;
61.27 +import org.sonews.util.Pair;
61.28 +
61.29 +/**
61.30 + * An aggregated group is a group consisting of several "real" group.
61.31 + * @author Christian Lins
61.32 + * @since sonews/1.0
61.33 + */
61.34 +class AggregatedGroup extends Channel
61.35 +{
61.36 +
61.37 + static class GroupElement
61.38 + {
61.39 + private Group group;
61.40 + private long offsetStart, offsetEnd;
61.41 +
61.42 + public GroupElement(Group group, long offsetStart, long offsetEnd)
61.43 + {
61.44 + this.group = group;
61.45 + this.offsetEnd = offsetEnd;
61.46 + this.offsetStart = offsetStart;
61.47 + }
61.48 + }
61.49 +
61.50 + public static List<Channel> getAll()
61.51 + {
61.52 + List<Channel> all = new ArrayList<Channel>();
61.53 + all.add(getByName("agg.test"));
61.54 + return all;
61.55 + }
61.56 +
61.57 + public static AggregatedGroup getByName(String name)
61.58 + {
61.59 + if("agg.test".equals(name))
61.60 + {
61.61 + AggregatedGroup agroup = new AggregatedGroup(name);
61.62 + agroup.addGroup(Group.getByName("agg.test0"), 0, 1000);
61.63 + agroup.addGroup(Group.getByName("agg.test1"), 2000, 4000);
61.64 + return agroup;
61.65 + }
61.66 + else
61.67 + return null;
61.68 + }
61.69 +
61.70 + private GroupElement[] groups = new GroupElement[2];
61.71 + private String name;
61.72 +
61.73 + public AggregatedGroup(String name)
61.74 + {
61.75 + this.name = name;
61.76 + }
61.77 +
61.78 + private long aggIdxToIdx(long aggIdx)
61.79 + throws StorageBackendException
61.80 + {
61.81 + assert groups != null && groups.length == 2;
61.82 + assert groups[0] != null;
61.83 + assert groups[1] != null;
61.84 +
61.85 + // Search in indices of group one
61.86 + List<Long> idxs0 = groups[0].group.getArticleNumbers();
61.87 + Collections.sort(idxs0);
61.88 + for(long idx : idxs0)
61.89 + {
61.90 + if(idx == aggIdx)
61.91 + {
61.92 + return idx;
61.93 + }
61.94 + }
61.95 +
61.96 + // Given aggIdx must be an index of group two
61.97 + List<Long> idxs1 = groups[1].group.getArticleNumbers();
61.98 + return 0;
61.99 + }
61.100 +
61.101 + private long idxToAggIdx(long idx)
61.102 + {
61.103 + return 0;
61.104 + }
61.105 +
61.106 + /**
61.107 + * Adds the given group to this aggregated set.
61.108 + * @param group
61.109 + * @param offsetStart Lower limit for the article ids range
61.110 + */
61.111 + public void addGroup(Group group, long offsetStart, long offsetEnd)
61.112 + {
61.113 + this.groups[groups[0] == null ? 0 : 1]
61.114 + = new GroupElement(group, offsetStart, offsetEnd);
61.115 + }
61.116 +
61.117 + @Override
61.118 + public Article getArticle(long idx)
61.119 + throws StorageBackendException
61.120 + {
61.121 + Article article = null;
61.122 +
61.123 + for(GroupElement groupEl : groups)
61.124 + {
61.125 + if(groupEl.offsetStart <= idx && groupEl.offsetEnd >= idx)
61.126 + {
61.127 + article = groupEl.group.getArticle(idx - groupEl.offsetStart);
61.128 + break;
61.129 + }
61.130 + }
61.131 +
61.132 + return article;
61.133 + }
61.134 +
61.135 + @Override
61.136 + public List<Pair<Long, ArticleHead>> getArticleHeads(
61.137 + final long first, final long last)
61.138 + throws StorageBackendException
61.139 + {
61.140 + List<Pair<Long, ArticleHead>> heads = new ArrayList<Pair<Long, ArticleHead>>();
61.141 +
61.142 + for(GroupElement groupEl : groups)
61.143 + {
61.144 + List<Pair<Long, ArticleHead>> partHeads = new ArrayList<Pair<Long, ArticleHead>>();
61.145 + if(groupEl.offsetStart <= first && groupEl.offsetEnd >= first)
61.146 + {
61.147 + long end = Math.min(groupEl.offsetEnd, last);
61.148 + partHeads = groupEl.group.getArticleHeads
61.149 + (first - groupEl.offsetStart, end - groupEl.offsetStart);
61.150 + }
61.151 + else if(groupEl.offsetStart <= last && groupEl.offsetEnd >= last)
61.152 + {
61.153 + long start = Math.max(groupEl.offsetStart, first);
61.154 + partHeads = groupEl.group.getArticleHeads
61.155 + (start - groupEl.offsetStart, last - groupEl.offsetStart);
61.156 + }
61.157 +
61.158 + for(Pair<Long, ArticleHead> partHead : partHeads)
61.159 + {
61.160 + heads.add(new Pair<Long, ArticleHead>(
61.161 + partHead.getA() + groupEl.offsetStart, partHead.getB()));
61.162 + }
61.163 + }
61.164 +
61.165 + return heads;
61.166 + }
61.167 +
61.168 + @Override
61.169 + public List<Long> getArticleNumbers()
61.170 + throws StorageBackendException
61.171 + {
61.172 + List<Long> articleNumbers = new ArrayList<Long>();
61.173 +
61.174 + for(GroupElement groupEl : groups)
61.175 + {
61.176 + List<Long> partNums = groupEl.group.getArticleNumbers();
61.177 + for(Long partNum : partNums)
61.178 + {
61.179 + articleNumbers.add(partNum + groupEl.offsetStart);
61.180 + }
61.181 + }
61.182 +
61.183 + return articleNumbers;
61.184 + }
61.185 +
61.186 + @Override
61.187 + public long getIndexOf(Article art)
61.188 + throws StorageBackendException
61.189 + {
61.190 + for(GroupElement groupEl : groups)
61.191 + {
61.192 + long idx = groupEl.group.getIndexOf(art);
61.193 + if(idx > 0)
61.194 + {
61.195 + return idx;
61.196 + }
61.197 + }
61.198 + return -1;
61.199 + }
61.200 +
61.201 + public long getInternalID()
61.202 + {
61.203 + return -1;
61.204 + }
61.205 +
61.206 + @Override
61.207 + public String getName()
61.208 + {
61.209 + return this.name;
61.210 + }
61.211 +
61.212 + @Override
61.213 + public long getFirstArticleNumber()
61.214 + throws StorageBackendException
61.215 + {
61.216 + long first = Long.MAX_VALUE;
61.217 +
61.218 + for(GroupElement groupEl : groups)
61.219 + {
61.220 + first = Math.min(first, groupEl.group.getFirstArticleNumber() + groupEl.offsetStart);
61.221 + }
61.222 +
61.223 + return first;
61.224 + }
61.225 +
61.226 + @Override
61.227 + public long getLastArticleNumber()
61.228 + throws StorageBackendException
61.229 + {
61.230 + long last = 1;
61.231 +
61.232 + for(GroupElement groupEl : groups)
61.233 + {
61.234 + last = Math.max(last, groupEl.group.getLastArticleNumber() + groupEl.offsetStart);
61.235 + }
61.236 +
61.237 + return last + getPostingsCount(); // This is a hack
61.238 + }
61.239 +
61.240 + public long getPostingsCount()
61.241 + throws StorageBackendException
61.242 + {
61.243 + long postings = 0;
61.244 +
61.245 + for(GroupElement groupEl : groups)
61.246 + {
61.247 + postings += groupEl.group.getPostingsCount();
61.248 + }
61.249 +
61.250 + return postings;
61.251 + }
61.252 +
61.253 + public boolean isDeleted()
61.254 + {
61.255 + return false;
61.256 + }
61.257 +
61.258 + public boolean isWriteable()
61.259 + {
61.260 + return false;
61.261 + }
61.262 +
61.263 +}
62.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
62.2 +++ b/org/sonews/storage/Article.java Wed Jul 22 14:04:05 2009 +0200
62.3 @@ -0,0 +1,336 @@
62.4 +/*
62.5 + * SONEWS News Server
62.6 + * see AUTHORS for the list of contributors
62.7 + *
62.8 + * This program is free software: you can redistribute it and/or modify
62.9 + * it under the terms of the GNU General Public License as published by
62.10 + * the Free Software Foundation, either version 3 of the License, or
62.11 + * (at your option) any later version.
62.12 + *
62.13 + * This program is distributed in the hope that it will be useful,
62.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
62.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
62.16 + * GNU General Public License for more details.
62.17 + *
62.18 + * You should have received a copy of the GNU General Public License
62.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
62.20 + */
62.21 +
62.22 +package org.sonews.storage;
62.23 +
62.24 +import java.io.ByteArrayInputStream;
62.25 +import java.io.ByteArrayOutputStream;
62.26 +import java.io.IOException;
62.27 +import java.io.InputStream;
62.28 +import java.nio.charset.Charset;
62.29 +import java.security.MessageDigest;
62.30 +import java.security.NoSuchAlgorithmException;
62.31 +import java.util.UUID;
62.32 +import java.util.ArrayList;
62.33 +import java.util.Enumeration;
62.34 +import java.util.List;
62.35 +import javax.mail.Header;
62.36 +import javax.mail.Message;
62.37 +import javax.mail.MessagingException;
62.38 +import javax.mail.Multipart;
62.39 +import javax.mail.internet.InternetHeaders;
62.40 +import org.sonews.config.Config;
62.41 +import org.sonews.util.Log;
62.42 +
62.43 +/**
62.44 + * Represents a newsgroup article.
62.45 + * @author Christian Lins
62.46 + * @author Denis Schwerdel
62.47 + * @since n3tpd/0.1
62.48 + */
62.49 +public class Article extends ArticleHead
62.50 +{
62.51 +
62.52 + /**
62.53 + * Loads the Article identified by the given ID from the JDBCDatabase.
62.54 + * @param messageID
62.55 + * @return null if Article is not found or if an error occurred.
62.56 + */
62.57 + public static Article getByMessageID(final String messageID)
62.58 + {
62.59 + try
62.60 + {
62.61 + return StorageManager.current().getArticle(messageID);
62.62 + }
62.63 + catch(StorageBackendException ex)
62.64 + {
62.65 + ex.printStackTrace();
62.66 + return null;
62.67 + }
62.68 + }
62.69 +
62.70 + private byte[] body = new byte[0];
62.71 +
62.72 + /**
62.73 + * Default constructor.
62.74 + */
62.75 + public Article()
62.76 + {
62.77 + }
62.78 +
62.79 + /**
62.80 + * Creates a new Article object using the date from the given
62.81 + * raw data.
62.82 + */
62.83 + public Article(String headers, byte[] body)
62.84 + {
62.85 + try
62.86 + {
62.87 + this.body = body;
62.88 +
62.89 + // Parse the header
62.90 + this.headers = new InternetHeaders(
62.91 + new ByteArrayInputStream(headers.getBytes()));
62.92 +
62.93 + this.headerSrc = headers;
62.94 + }
62.95 + catch(MessagingException ex)
62.96 + {
62.97 + ex.printStackTrace();
62.98 + }
62.99 + }
62.100 +
62.101 + /**
62.102 + * Creates an Article instance using the data from the javax.mail.Message
62.103 + * object.
62.104 + * @see javax.mail.Message
62.105 + * @param msg
62.106 + * @throws IOException
62.107 + * @throws MessagingException
62.108 + */
62.109 + public Article(final Message msg)
62.110 + throws IOException, MessagingException
62.111 + {
62.112 + this.headers = new InternetHeaders();
62.113 +
62.114 + for(Enumeration e = msg.getAllHeaders() ; e.hasMoreElements();)
62.115 + {
62.116 + final Header header = (Header)e.nextElement();
62.117 + this.headers.addHeader(header.getName(), header.getValue());
62.118 + }
62.119 +
62.120 + // The "content" of the message can be a String if it's a simple text/plain
62.121 + // message, a Multipart object or an InputStream if the content is unknown.
62.122 + final Object content = msg.getContent();
62.123 + if(content instanceof String)
62.124 + {
62.125 + this.body = ((String)content).getBytes();
62.126 + }
62.127 + else if(content instanceof Multipart) // probably subclass MimeMultipart
62.128 + {
62.129 + // We're are not interested in the different parts of the MultipartMessage,
62.130 + // so we simply read in all data which *can* be huge.
62.131 + InputStream in = msg.getInputStream();
62.132 + this.body = readContent(in);
62.133 + }
62.134 + else if(content instanceof InputStream)
62.135 + {
62.136 + // The message format is unknown to the Message class, but we can
62.137 + // simply read in the whole message data.
62.138 + this.body = readContent((InputStream)content);
62.139 + }
62.140 + else
62.141 + {
62.142 + // Unknown content is probably a malformed mail we should skip.
62.143 + // On the other hand we produce an inconsistent mail mirror, but no
62.144 + // mail system must transport invalid content.
62.145 + Log.msg("Skipping message due to unknown content. Throwing exception...", true);
62.146 + throw new MessagingException("Unknown content: " + content);
62.147 + }
62.148 +
62.149 + // Validate headers
62.150 + validateHeaders();
62.151 + }
62.152 +
62.153 + /**
62.154 + * Reads from the given InputString into a byte array.
62.155 + * TODO: Move this generalized method to org.sonews.util.io.Resource.
62.156 + * @param in
62.157 + * @return
62.158 + * @throws IOException
62.159 + */
62.160 + private byte[] readContent(InputStream in)
62.161 + throws IOException
62.162 + {
62.163 + ByteArrayOutputStream out = new ByteArrayOutputStream();
62.164 +
62.165 + int b = in.read();
62.166 + while(b >= 0)
62.167 + {
62.168 + out.write(b);
62.169 + b = in.read();
62.170 + }
62.171 +
62.172 + return out.toByteArray();
62.173 + }
62.174 +
62.175 + /**
62.176 + * Removes the header identified by the given key.
62.177 + * @param headerKey
62.178 + */
62.179 + public void removeHeader(final String headerKey)
62.180 + {
62.181 + this.headers.removeHeader(headerKey);
62.182 + this.headerSrc = null;
62.183 + }
62.184 +
62.185 + /**
62.186 + * Generates a message id for this article and sets it into
62.187 + * the header object. You have to update the JDBCDatabase manually to make this
62.188 + * change persistent.
62.189 + * Note: a Message-ID should never be changed and only generated once.
62.190 + */
62.191 + private String generateMessageID()
62.192 + {
62.193 + String randomString;
62.194 + MessageDigest md5;
62.195 + try
62.196 + {
62.197 + md5 = MessageDigest.getInstance("MD5");
62.198 + md5.reset();
62.199 + md5.update(getBody());
62.200 + md5.update(getHeader(Headers.SUBJECT)[0].getBytes());
62.201 + md5.update(getHeader(Headers.FROM)[0].getBytes());
62.202 + byte[] result = md5.digest();
62.203 + StringBuffer hexString = new StringBuffer();
62.204 + for (int i = 0; i < result.length; i++)
62.205 + {
62.206 + hexString.append(Integer.toHexString(0xFF & result[i]));
62.207 + }
62.208 + randomString = hexString.toString();
62.209 + }
62.210 + catch (NoSuchAlgorithmException e)
62.211 + {
62.212 + e.printStackTrace();
62.213 + randomString = UUID.randomUUID().toString();
62.214 + }
62.215 + String msgID = "<" + randomString + "@"
62.216 + + Config.inst().get(Config.HOSTNAME, "localhost") + ">";
62.217 +
62.218 + this.headers.setHeader(Headers.MESSAGE_ID, msgID);
62.219 +
62.220 + return msgID;
62.221 + }
62.222 +
62.223 + /**
62.224 + * Returns the body string.
62.225 + */
62.226 + public byte[] getBody()
62.227 + {
62.228 + return body;
62.229 + }
62.230 +
62.231 + /**
62.232 + * @return Charset of the body text
62.233 + */
62.234 + private Charset getBodyCharset()
62.235 + {
62.236 + // We espect something like
62.237 + // Content-Type: text/plain; charset=ISO-8859-15
62.238 + String contentType = getHeader(Headers.CONTENT_TYPE)[0];
62.239 + int idxCharsetStart = contentType.indexOf("charset=") + "charset=".length();
62.240 + int idxCharsetEnd = contentType.indexOf(";", idxCharsetStart);
62.241 +
62.242 + String charsetName = "UTF-8";
62.243 + if(idxCharsetStart >= 0 && idxCharsetStart < contentType.length())
62.244 + {
62.245 + if(idxCharsetEnd < 0)
62.246 + {
62.247 + charsetName = contentType.substring(idxCharsetStart);
62.248 + }
62.249 + else
62.250 + {
62.251 + charsetName = contentType.substring(idxCharsetStart, idxCharsetEnd);
62.252 + }
62.253 + }
62.254 +
62.255 + // Sometimes there are '"' around the name
62.256 + if(charsetName.length() > 2 &&
62.257 + charsetName.charAt(0) == '"' && charsetName.endsWith("\""))
62.258 + {
62.259 + charsetName = charsetName.substring(1, charsetName.length() - 2);
62.260 + }
62.261 +
62.262 + // Create charset
62.263 + Charset charset = Charset.forName("UTF-8"); // This MUST be supported by JVM
62.264 + try
62.265 + {
62.266 + charset = Charset.forName(charsetName);
62.267 + }
62.268 + catch(Exception ex)
62.269 + {
62.270 + Log.msg(ex.getMessage(), false);
62.271 + Log.msg("Article.getBodyCharset(): Unknown charset: " + charsetName, false);
62.272 + }
62.273 + return charset;
62.274 + }
62.275 +
62.276 + /**
62.277 + * @return Numerical IDs of the newsgroups this Article belongs to.
62.278 + */
62.279 + public List<Group> getGroups()
62.280 + {
62.281 + String[] groupnames = getHeader(Headers.NEWSGROUPS)[0].split(",");
62.282 + ArrayList<Group> groups = new ArrayList<Group>();
62.283 +
62.284 + try
62.285 + {
62.286 + for(String newsgroup : groupnames)
62.287 + {
62.288 + newsgroup = newsgroup.trim();
62.289 + Group group = StorageManager.current().getGroup(newsgroup);
62.290 + if(group != null && // If the server does not provide the group, ignore it
62.291 + !groups.contains(group)) // Yes, there may be duplicates
62.292 + {
62.293 + groups.add(group);
62.294 + }
62.295 + }
62.296 + }
62.297 + catch(StorageBackendException ex)
62.298 + {
62.299 + ex.printStackTrace();
62.300 + return null;
62.301 + }
62.302 + return groups;
62.303 + }
62.304 +
62.305 + public void setBody(byte[] body)
62.306 + {
62.307 + this.body = body;
62.308 + }
62.309 +
62.310 + /**
62.311 + *
62.312 + * @param groupname Name(s) of newsgroups
62.313 + */
62.314 + public void setGroup(String groupname)
62.315 + {
62.316 + this.headers.setHeader(Headers.NEWSGROUPS, groupname);
62.317 + }
62.318 +
62.319 + /**
62.320 + * Returns the Message-ID of this Article. If the appropriate header
62.321 + * is empty, a new Message-ID is created.
62.322 + * @return Message-ID of this Article.
62.323 + */
62.324 + public String getMessageID()
62.325 + {
62.326 + String[] msgID = getHeader(Headers.MESSAGE_ID);
62.327 + return msgID[0].equals("") ? generateMessageID() : msgID[0];
62.328 + }
62.329 +
62.330 + /**
62.331 + * @return String containing the Message-ID.
62.332 + */
62.333 + @Override
62.334 + public String toString()
62.335 + {
62.336 + return getMessageID();
62.337 + }
62.338 +
62.339 +}
63.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
63.2 +++ b/org/sonews/storage/ArticleHead.java Wed Jul 22 14:04:05 2009 +0200
63.3 @@ -0,0 +1,161 @@
63.4 +/*
63.5 + * SONEWS News Server
63.6 + * see AUTHORS for the list of contributors
63.7 + *
63.8 + * This program is free software: you can redistribute it and/or modify
63.9 + * it under the terms of the GNU General Public License as published by
63.10 + * the Free Software Foundation, either version 3 of the License, or
63.11 + * (at your option) any later version.
63.12 + *
63.13 + * This program is distributed in the hope that it will be useful,
63.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
63.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
63.16 + * GNU General Public License for more details.
63.17 + *
63.18 + * You should have received a copy of the GNU General Public License
63.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
63.20 + */
63.21 +
63.22 +package org.sonews.storage;
63.23 +
63.24 +import java.io.ByteArrayInputStream;
63.25 +import java.util.Enumeration;
63.26 +import javax.mail.Header;
63.27 +import javax.mail.MessagingException;
63.28 +import javax.mail.internet.InternetHeaders;
63.29 +import javax.mail.internet.MimeUtility;
63.30 +import org.sonews.config.Config;
63.31 +
63.32 +/**
63.33 + * An article with no body only headers.
63.34 + * @author Christian Lins
63.35 + * @since sonews/0.5.0
63.36 + */
63.37 +public class ArticleHead
63.38 +{
63.39 +
63.40 + protected InternetHeaders headers = null;
63.41 + protected String headerSrc = null;
63.42 +
63.43 + protected ArticleHead()
63.44 + {
63.45 + }
63.46 +
63.47 + public ArticleHead(String headers)
63.48 + {
63.49 + try
63.50 + {
63.51 + // Parse the header
63.52 + this.headers = new InternetHeaders(
63.53 + new ByteArrayInputStream(headers.getBytes()));
63.54 + }
63.55 + catch(MessagingException ex)
63.56 + {
63.57 + ex.printStackTrace();
63.58 + }
63.59 + }
63.60 +
63.61 + /**
63.62 + * Returns the header field with given name.
63.63 + * @param name Name of the header field(s).
63.64 + * @param returnNull If set to true, this method will return null instead
63.65 + * of an empty array if there is no header field found.
63.66 + * @return Header values or empty string.
63.67 + */
63.68 + public String[] getHeader(String name, boolean returnNull)
63.69 + {
63.70 + String[] ret = this.headers.getHeader(name);
63.71 + if(ret == null && !returnNull)
63.72 + {
63.73 + ret = new String[]{""};
63.74 + }
63.75 + return ret;
63.76 + }
63.77 +
63.78 + public String[] getHeader(String name)
63.79 + {
63.80 + return getHeader(name, false);
63.81 + }
63.82 +
63.83 + /**
63.84 + * Sets the header value identified through the header name.
63.85 + * @param name
63.86 + * @param value
63.87 + */
63.88 + public void setHeader(String name, String value)
63.89 + {
63.90 + this.headers.setHeader(name, value);
63.91 + this.headerSrc = null;
63.92 + }
63.93 +
63.94 + public Enumeration getAllHeaders()
63.95 + {
63.96 + return this.headers.getAllHeaders();
63.97 + }
63.98 +
63.99 + /**
63.100 + * @return Header source code of this Article.
63.101 + */
63.102 + public String getHeaderSource()
63.103 + {
63.104 + if(this.headerSrc != null)
63.105 + {
63.106 + return this.headerSrc;
63.107 + }
63.108 +
63.109 + StringBuffer buf = new StringBuffer();
63.110 +
63.111 + for(Enumeration en = this.headers.getAllHeaders(); en.hasMoreElements();)
63.112 + {
63.113 + Header entry = (Header)en.nextElement();
63.114 +
63.115 + String value = entry.getValue().replaceAll("[\r\n]", " ");
63.116 + buf.append(entry.getName());
63.117 + buf.append(": ");
63.118 + buf.append(MimeUtility.fold(entry.getName().length() + 2, value));
63.119 +
63.120 + if(en.hasMoreElements())
63.121 + {
63.122 + buf.append("\r\n");
63.123 + }
63.124 + }
63.125 +
63.126 + this.headerSrc = buf.toString();
63.127 + return this.headerSrc;
63.128 + }
63.129 +
63.130 + /**
63.131 + * Sets the headers of this Article. If headers contain no
63.132 + * Message-Id a new one is created.
63.133 + * @param headers
63.134 + */
63.135 + public void setHeaders(InternetHeaders headers)
63.136 + {
63.137 + this.headers = headers;
63.138 + this.headerSrc = null;
63.139 + validateHeaders();
63.140 + }
63.141 +
63.142 + /**
63.143 + * Checks some headers for their validity and generates an
63.144 + * appropriate Path-header for this host if not yet existing.
63.145 + * This method is called by some Article constructors and the
63.146 + * method setHeaders().
63.147 + * @return true if something on the headers was changed.
63.148 + */
63.149 + protected void validateHeaders()
63.150 + {
63.151 + // Check for valid Path-header
63.152 + final String path = getHeader(Headers.PATH)[0];
63.153 + final String host = Config.inst().get(Config.HOSTNAME, "localhost");
63.154 + if(!path.startsWith(host))
63.155 + {
63.156 + StringBuffer pathBuf = new StringBuffer();
63.157 + pathBuf.append(host);
63.158 + pathBuf.append('!');
63.159 + pathBuf.append(path);
63.160 + this.headers.setHeader(Headers.PATH, pathBuf.toString());
63.161 + }
63.162 + }
63.163 +
63.164 +}
64.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
64.2 +++ b/org/sonews/storage/Channel.java Wed Jul 22 14:04:05 2009 +0200
64.3 @@ -0,0 +1,121 @@
64.4 +/*
64.5 + * SONEWS News Server
64.6 + * see AUTHORS for the list of contributors
64.7 + *
64.8 + * This program is free software: you can redistribute it and/or modify
64.9 + * it under the terms of the GNU General Public License as published by
64.10 + * the Free Software Foundation, either version 3 of the License, or
64.11 + * (at your option) any later version.
64.12 + *
64.13 + * This program is distributed in the hope that it will be useful,
64.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
64.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
64.16 + * GNU General Public License for more details.
64.17 + *
64.18 + * You should have received a copy of the GNU General Public License
64.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
64.20 + */
64.21 +
64.22 +package org.sonews.storage;
64.23 +
64.24 +import java.util.ArrayList;
64.25 +import java.util.List;
64.26 +import org.sonews.util.Pair;
64.27 +
64.28 +/**
64.29 + * A logical communication Channel is the a generic structural element for sets
64.30 + * of messages; e.g. a Newsgroup for a set of Articles.
64.31 + * A Channel can either be a real set of messages or an aggregated set of
64.32 + * several subsets.
64.33 + * @author Christian Lins
64.34 + * @since sonews/1.0
64.35 + */
64.36 +public abstract class Channel
64.37 +{
64.38 +
64.39 + /**
64.40 + * If this flag is set the Group is no real newsgroup but a mailing list
64.41 + * mirror. In that case every posting and receiving mails must go through
64.42 + * the mailing list gateway.
64.43 + */
64.44 + public static final int MAILINGLIST = 0x1;
64.45 +
64.46 + /**
64.47 + * If this flag is set the Group is marked as readonly and the posting
64.48 + * is prohibited. This can be useful for groups that are synced only in
64.49 + * one direction.
64.50 + */
64.51 + public static final int READONLY = 0x2;
64.52 +
64.53 + /**
64.54 + * If this flag is set the Group is marked as deleted and must not occur
64.55 + * in any output. The deletion is done lazily by a low priority daemon.
64.56 + */
64.57 + public static final int DELETED = 0x80;
64.58 +
64.59 + public static List<Channel> getAll()
64.60 + {
64.61 + List<Channel> all = new ArrayList<Channel>();
64.62 +
64.63 + /*List<Channel> agroups = AggregatedGroup.getAll();
64.64 + if(agroups != null)
64.65 + {
64.66 + all.addAll(agroups);
64.67 + }*/
64.68 +
64.69 + List<Channel> groups = Group.getAll();
64.70 + if(groups != null)
64.71 + {
64.72 + all.addAll(groups);
64.73 + }
64.74 +
64.75 + return all;
64.76 + }
64.77 +
64.78 + public static Channel getByName(String name)
64.79 + {
64.80 + Channel channel;
64.81 +
64.82 + // Check if it's an aggregated group
64.83 + channel = AggregatedGroup.getByName(name);
64.84 +
64.85 + // If it's not an aggregate is probably a "real" group
64.86 + if(channel == null)
64.87 + {
64.88 + channel = Group.getByName(name);
64.89 + }
64.90 +
64.91 + return channel;
64.92 + }
64.93 +
64.94 + public abstract Article getArticle(long idx)
64.95 + throws StorageBackendException;
64.96 +
64.97 + public abstract List<Pair<Long, ArticleHead>> getArticleHeads(
64.98 + final long first, final long last)
64.99 + throws StorageBackendException;
64.100 +
64.101 + public abstract List<Long> getArticleNumbers()
64.102 + throws StorageBackendException;
64.103 +
64.104 + public abstract long getFirstArticleNumber()
64.105 + throws StorageBackendException;
64.106 +
64.107 + public abstract long getIndexOf(Article art)
64.108 + throws StorageBackendException;
64.109 +
64.110 + public abstract long getInternalID();
64.111 +
64.112 + public abstract long getLastArticleNumber()
64.113 + throws StorageBackendException;
64.114 +
64.115 + public abstract String getName();
64.116 +
64.117 + public abstract long getPostingsCount()
64.118 + throws StorageBackendException;
64.119 +
64.120 + public abstract boolean isDeleted();
64.121 +
64.122 + public abstract boolean isWriteable();
64.123 +
64.124 +}
65.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
65.2 +++ b/org/sonews/storage/Group.java Wed Jul 22 14:04:05 2009 +0200
65.3 @@ -0,0 +1,202 @@
65.4 +/*
65.5 + * SONEWS News Server
65.6 + * see AUTHORS for the list of contributors
65.7 + *
65.8 + * This program is free software: you can redistribute it and/or modify
65.9 + * it under the terms of the GNU General Public License as published by
65.10 + * the Free Software Foundation, either version 3 of the License, or
65.11 + * (at your option) any later version.
65.12 + *
65.13 + * This program is distributed in the hope that it will be useful,
65.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
65.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
65.16 + * GNU General Public License for more details.
65.17 + *
65.18 + * You should have received a copy of the GNU General Public License
65.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
65.20 + */
65.21 +
65.22 +package org.sonews.storage;
65.23 +
65.24 +import java.sql.SQLException;
65.25 +import java.util.List;
65.26 +import org.sonews.util.Log;
65.27 +import org.sonews.util.Pair;
65.28 +
65.29 +/**
65.30 + * Represents a logical Group within this newsserver.
65.31 + * @author Christian Lins
65.32 + * @since sonews/0.5.0
65.33 + */
65.34 +// TODO: This class should not be public!
65.35 +public class Group extends Channel
65.36 +{
65.37 +
65.38 + private long id = 0;
65.39 + private int flags = -1;
65.40 + private String name = null;
65.41 +
65.42 + /**
65.43 + * Returns a Group identified by its full name.
65.44 + * @param name
65.45 + * @return
65.46 + */
65.47 + public static Group getByName(final String name)
65.48 + {
65.49 + try
65.50 + {
65.51 + return StorageManager.current().getGroup(name);
65.52 + }
65.53 + catch(StorageBackendException ex)
65.54 + {
65.55 + ex.printStackTrace();
65.56 + return null;
65.57 + }
65.58 + }
65.59 +
65.60 + /**
65.61 + * @return List of all groups this server handles.
65.62 + */
65.63 + public static List<Channel> getAll()
65.64 + {
65.65 + try
65.66 + {
65.67 + return StorageManager.current().getGroups();
65.68 + }
65.69 + catch(StorageBackendException ex)
65.70 + {
65.71 + Log.msg(ex.getMessage(), false);
65.72 + return null;
65.73 + }
65.74 + }
65.75 +
65.76 + /**
65.77 + * @param name
65.78 + * @param id
65.79 + */
65.80 + public Group(final String name, final long id, final int flags)
65.81 + {
65.82 + this.id = id;
65.83 + this.flags = flags;
65.84 + this.name = name;
65.85 + }
65.86 +
65.87 + @Override
65.88 + public boolean equals(Object obj)
65.89 + {
65.90 + if(obj instanceof Group)
65.91 + {
65.92 + return ((Group)obj).id == this.id;
65.93 + }
65.94 + else
65.95 + {
65.96 + return false;
65.97 + }
65.98 + }
65.99 +
65.100 + public Article getArticle(long idx)
65.101 + throws StorageBackendException
65.102 + {
65.103 + return StorageManager.current().getArticle(idx, this.id);
65.104 + }
65.105 +
65.106 + public List<Pair<Long, ArticleHead>> getArticleHeads(final long first, final long last)
65.107 + throws StorageBackendException
65.108 + {
65.109 + return StorageManager.current().getArticleHeads(this, first, last);
65.110 + }
65.111 +
65.112 + public List<Long> getArticleNumbers()
65.113 + throws StorageBackendException
65.114 + {
65.115 + return StorageManager.current().getArticleNumbers(id);
65.116 + }
65.117 +
65.118 + public long getFirstArticleNumber()
65.119 + throws StorageBackendException
65.120 + {
65.121 + return StorageManager.current().getFirstArticleNumber(this);
65.122 + }
65.123 +
65.124 + public int getFlags()
65.125 + {
65.126 + return this.flags;
65.127 + }
65.128 +
65.129 + public long getIndexOf(Article art)
65.130 + throws StorageBackendException
65.131 + {
65.132 + return StorageManager.current().getArticleIndex(art, this);
65.133 + }
65.134 +
65.135 + /**
65.136 + * Returns the group id.
65.137 + */
65.138 + public long getInternalID()
65.139 + {
65.140 + assert id > 0;
65.141 +
65.142 + return id;
65.143 + }
65.144 +
65.145 + public boolean isDeleted()
65.146 + {
65.147 + return (this.flags & DELETED) != 0;
65.148 + }
65.149 +
65.150 + public boolean isMailingList()
65.151 + {
65.152 + return (this.flags & MAILINGLIST) != 0;
65.153 + }
65.154 +
65.155 + public boolean isWriteable()
65.156 + {
65.157 + return true;
65.158 + }
65.159 +
65.160 + public long getLastArticleNumber()
65.161 + throws StorageBackendException
65.162 + {
65.163 + return StorageManager.current().getLastArticleNumber(this);
65.164 + }
65.165 +
65.166 + public String getName()
65.167 + {
65.168 + return name;
65.169 + }
65.170 +
65.171 + /**
65.172 + * Performs this.flags |= flag to set a specified flag and updates the data
65.173 + * in the JDBCDatabase.
65.174 + * @param flag
65.175 + */
65.176 + public void setFlag(final int flag)
65.177 + {
65.178 + this.flags |= flag;
65.179 + }
65.180 +
65.181 + public void setName(final String name)
65.182 + {
65.183 + this.name = name;
65.184 + }
65.185 +
65.186 + /**
65.187 + * @return Number of posted articles in this group.
65.188 + * @throws java.sql.SQLException
65.189 + */
65.190 + public long getPostingsCount()
65.191 + throws StorageBackendException
65.192 + {
65.193 + return StorageManager.current().getPostingsCount(this.name);
65.194 + }
65.195 +
65.196 + /**
65.197 + * Updates flags and name in the backend.
65.198 + */
65.199 + public void update()
65.200 + throws StorageBackendException
65.201 + {
65.202 + StorageManager.current().update(this);
65.203 + }
65.204 +
65.205 +}
66.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
66.2 +++ b/org/sonews/storage/Headers.java Wed Jul 22 14:04:05 2009 +0200
66.3 @@ -0,0 +1,54 @@
66.4 +/*
66.5 + * SONEWS News Server
66.6 + * see AUTHORS for the list of contributors
66.7 + *
66.8 + * This program is free software: you can redistribute it and/or modify
66.9 + * it under the terms of the GNU General Public License as published by
66.10 + * the Free Software Foundation, either version 3 of the License, or
66.11 + * (at your option) any later version.
66.12 + *
66.13 + * This program is distributed in the hope that it will be useful,
66.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
66.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
66.16 + * GNU General Public License for more details.
66.17 + *
66.18 + * You should have received a copy of the GNU General Public License
66.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
66.20 + */
66.21 +
66.22 +package org.sonews.storage;
66.23 +
66.24 +/**
66.25 + * Contains header constants. These header keys are no way complete but all
66.26 + * headers that are relevant for sonews.
66.27 + * @author Christian Lins
66.28 + * @since sonews/0.5.0
66.29 + */
66.30 +public final class Headers
66.31 +{
66.32 +
66.33 + public static final String BYTES = "bytes";
66.34 + public static final String CONTENT_TYPE = "content-type";
66.35 + public static final String CONTROL = "control";
66.36 + public static final String DATE = "date";
66.37 + public static final String FROM = "from";
66.38 + public static final String LINES = "lines";
66.39 + public static final String MESSAGE_ID = "message-id";
66.40 + public static final String NEWSGROUPS = "newsgroups";
66.41 + public static final String NNTP_POSTING_DATE = "nntp-posting-date";
66.42 + public static final String NNTP_POSTING_HOST = "nntp-posting-host";
66.43 + public static final String PATH = "path";
66.44 + public static final String REFERENCES = "references";
66.45 + public static final String REPLY_TO = "reply-to";
66.46 + public static final String SENDER = "sender";
66.47 + public static final String SUBJECT = "subject";
66.48 + public static final String SUPERSEDES = "subersedes";
66.49 + public static final String TO = "to";
66.50 + public static final String X_COMPLAINTS_TO = "x-complaints-to";
66.51 + public static final String X_TRACE = "x-trace";
66.52 + public static final String XREF = "xref";
66.53 +
66.54 + private Headers()
66.55 + {}
66.56 +
66.57 +}
67.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
67.2 +++ b/org/sonews/storage/Storage.java Wed Jul 22 14:04:05 2009 +0200
67.3 @@ -0,0 +1,123 @@
67.4 +/*
67.5 + * SONEWS News Server
67.6 + * see AUTHORS for the list of contributors
67.7 + *
67.8 + * This program is free software: you can redistribute it and/or modify
67.9 + * it under the terms of the GNU General Public License as published by
67.10 + * the Free Software Foundation, either version 3 of the License, or
67.11 + * (at your option) any later version.
67.12 + *
67.13 + * This program is distributed in the hope that it will be useful,
67.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
67.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
67.16 + * GNU General Public License for more details.
67.17 + *
67.18 + * You should have received a copy of the GNU General Public License
67.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
67.20 + */
67.21 +
67.22 +package org.sonews.storage;
67.23 +
67.24 +import java.util.List;
67.25 +import javax.mail.internet.InternetAddress;
67.26 +import org.sonews.feed.Subscription;
67.27 +import org.sonews.util.Pair;
67.28 +
67.29 +/**
67.30 + * A generic storage backend interface.
67.31 + * @author Christian Lins
67.32 + * @since sonews/1.0
67.33 + */
67.34 +public interface Storage
67.35 +{
67.36 +
67.37 + void addArticle(Article art)
67.38 + throws StorageBackendException;
67.39 +
67.40 + void addEvent(long timestamp, int type, long groupID)
67.41 + throws StorageBackendException;
67.42 +
67.43 + void addGroup(String groupname, int flags)
67.44 + throws StorageBackendException;
67.45 +
67.46 + int countArticles()
67.47 + throws StorageBackendException;
67.48 +
67.49 + int countGroups()
67.50 + throws StorageBackendException;
67.51 +
67.52 + void delete(String messageID)
67.53 + throws StorageBackendException;
67.54 +
67.55 + Article getArticle(String messageID)
67.56 + throws StorageBackendException;
67.57 +
67.58 + Article getArticle(long articleIndex, long groupID)
67.59 + throws StorageBackendException;
67.60 +
67.61 + List<Pair<Long, ArticleHead>> getArticleHeads(Group group, long first, long last)
67.62 + throws StorageBackendException;
67.63 +
67.64 + List<Pair<Long, String>> getArticleHeaders(Channel channel, long start, long end,
67.65 + String header, String pattern)
67.66 + throws StorageBackendException;
67.67 +
67.68 + long getArticleIndex(Article art, Group group)
67.69 + throws StorageBackendException;
67.70 +
67.71 + List<Long> getArticleNumbers(long groupID)
67.72 + throws StorageBackendException;
67.73 +
67.74 + String getConfigValue(String key)
67.75 + throws StorageBackendException;
67.76 +
67.77 + int getEventsCount(int eventType, long startTimestamp, long endTimestamp,
67.78 + Channel channel)
67.79 + throws StorageBackendException;
67.80 +
67.81 + double getEventsPerHour(int key, long gid)
67.82 + throws StorageBackendException;
67.83 +
67.84 + int getFirstArticleNumber(Group group)
67.85 + throws StorageBackendException;
67.86 +
67.87 + Group getGroup(String name)
67.88 + throws StorageBackendException;
67.89 +
67.90 + List<Channel> getGroups()
67.91 + throws StorageBackendException;
67.92 +
67.93 + List<String> getGroupsForList(InternetAddress inetaddress)
67.94 + throws StorageBackendException;
67.95 +
67.96 + int getLastArticleNumber(Group group)
67.97 + throws StorageBackendException;
67.98 +
67.99 + String getListForGroup(String groupname)
67.100 + throws StorageBackendException;
67.101 +
67.102 + String getOldestArticle()
67.103 + throws StorageBackendException;
67.104 +
67.105 + int getPostingsCount(String groupname)
67.106 + throws StorageBackendException;
67.107 +
67.108 + List<Subscription> getSubscriptions(int type)
67.109 + throws StorageBackendException;
67.110 +
67.111 + boolean isArticleExisting(String messageID)
67.112 + throws StorageBackendException;
67.113 +
67.114 + boolean isGroupExisting(String groupname)
67.115 + throws StorageBackendException;
67.116 +
67.117 + void purgeGroup(Group group)
67.118 + throws StorageBackendException;
67.119 +
67.120 + void setConfigValue(String key, String value)
67.121 + throws StorageBackendException;
67.122 +
67.123 + boolean update(Group group)
67.124 + throws StorageBackendException;
67.125 +
67.126 +}
68.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
68.2 +++ b/org/sonews/storage/StorageBackendException.java Wed Jul 22 14:04:05 2009 +0200
68.3 @@ -0,0 +1,34 @@
68.4 +/*
68.5 + * SONEWS News Server
68.6 + * see AUTHORS for the list of contributors
68.7 + *
68.8 + * This program is free software: you can redistribute it and/or modify
68.9 + * it under the terms of the GNU General Public License as published by
68.10 + * the Free Software Foundation, either version 3 of the License, or
68.11 + * (at your option) any later version.
68.12 + *
68.13 + * This program is distributed in the hope that it will be useful,
68.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
68.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
68.16 + * GNU General Public License for more details.
68.17 + *
68.18 + * You should have received a copy of the GNU General Public License
68.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
68.20 + */
68.21 +
68.22 +package org.sonews.storage;
68.23 +
68.24 +/**
68.25 + *
68.26 + * @author Christian Lins
68.27 + * @since sonews/1.0
68.28 + */
68.29 +public class StorageBackendException extends Exception
68.30 +{
68.31 +
68.32 + public StorageBackendException(Throwable cause)
68.33 + {
68.34 + super(cause);
68.35 + }
68.36 +
68.37 +}
69.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
69.2 +++ b/org/sonews/storage/StorageManager.java Wed Jul 22 14:04:05 2009 +0200
69.3 @@ -0,0 +1,89 @@
69.4 +/*
69.5 + * SONEWS News Server
69.6 + * see AUTHORS for the list of contributors
69.7 + *
69.8 + * This program is free software: you can redistribute it and/or modify
69.9 + * it under the terms of the GNU General Public License as published by
69.10 + * the Free Software Foundation, either version 3 of the License, or
69.11 + * (at your option) any later version.
69.12 + *
69.13 + * This program is distributed in the hope that it will be useful,
69.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
69.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
69.16 + * GNU General Public License for more details.
69.17 + *
69.18 + * You should have received a copy of the GNU General Public License
69.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
69.20 + */
69.21 +
69.22 +package org.sonews.storage;
69.23 +
69.24 +/**
69.25 + *
69.26 + * @author Christian Lins
69.27 + * @since sonews/1.0
69.28 + */
69.29 +public final class StorageManager
69.30 +{
69.31 +
69.32 + private static StorageProvider provider;
69.33 +
69.34 + public static Storage current()
69.35 + throws StorageBackendException
69.36 + {
69.37 + synchronized(StorageManager.class)
69.38 + {
69.39 + if(provider == null)
69.40 + {
69.41 + return null;
69.42 + }
69.43 + else
69.44 + {
69.45 + return provider.storage(Thread.currentThread());
69.46 + }
69.47 + }
69.48 + }
69.49 +
69.50 + public static StorageProvider loadProvider(String pluginClassName)
69.51 + {
69.52 + try
69.53 + {
69.54 + Class<?> clazz = Class.forName(pluginClassName);
69.55 + Object inst = clazz.newInstance();
69.56 + return (StorageProvider)inst;
69.57 + }
69.58 + catch(Exception ex)
69.59 + {
69.60 + System.err.println(ex);
69.61 + return null;
69.62 + }
69.63 + }
69.64 +
69.65 + /**
69.66 + * Sets the current storage provider.
69.67 + * @param provider
69.68 + */
69.69 + public static void enableProvider(StorageProvider provider)
69.70 + {
69.71 + synchronized(StorageManager.class)
69.72 + {
69.73 + if(StorageManager.provider != null)
69.74 + {
69.75 + disableProvider();
69.76 + }
69.77 + StorageManager.provider = provider;
69.78 + }
69.79 + }
69.80 +
69.81 + /**
69.82 + * Disables the current provider.
69.83 + */
69.84 + public static void disableProvider()
69.85 + {
69.86 + synchronized(StorageManager.class)
69.87 + {
69.88 + provider = null;
69.89 + }
69.90 + }
69.91 +
69.92 +}
70.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
70.2 +++ b/org/sonews/storage/StorageProvider.java Wed Jul 22 14:04:05 2009 +0200
70.3 @@ -0,0 +1,40 @@
70.4 +/*
70.5 + * SONEWS News Server
70.6 + * see AUTHORS for the list of contributors
70.7 + *
70.8 + * This program is free software: you can redistribute it and/or modify
70.9 + * it under the terms of the GNU General Public License as published by
70.10 + * the Free Software Foundation, either version 3 of the License, or
70.11 + * (at your option) any later version.
70.12 + *
70.13 + * This program is distributed in the hope that it will be useful,
70.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
70.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
70.16 + * GNU General Public License for more details.
70.17 + *
70.18 + * You should have received a copy of the GNU General Public License
70.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
70.20 + */
70.21 +
70.22 +package org.sonews.storage;
70.23 +
70.24 +/**
70.25 + *
70.26 + * @author Christian Lins
70.27 + * @since sonews/1.0
70.28 + */
70.29 +public interface StorageProvider
70.30 +{
70.31 +
70.32 + public boolean isSupported(String uri);
70.33 +
70.34 + /**
70.35 + * This method returns the reference to the associated storage.
70.36 + * The reference MAY be unique for each thread. In any case it MUST be
70.37 + * thread-safe to use this method.
70.38 + * @return The reference to the associated Storage.
70.39 + */
70.40 + public Storage storage(Thread thread)
70.41 + throws StorageBackendException;
70.42 +
70.43 +}
71.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
71.2 +++ b/org/sonews/storage/impl/JDBCDatabase.java Wed Jul 22 14:04:05 2009 +0200
71.3 @@ -0,0 +1,1772 @@
71.4 +/*
71.5 + * SONEWS News Server
71.6 + * see AUTHORS for the list of contributors
71.7 + *
71.8 + * This program is free software: you can redistribute it and/or modify
71.9 + * it under the terms of the GNU General Public License as published by
71.10 + * the Free Software Foundation, either version 3 of the License, or
71.11 + * (at your option) any later version.
71.12 + *
71.13 + * This program is distributed in the hope that it will be useful,
71.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
71.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
71.16 + * GNU General Public License for more details.
71.17 + *
71.18 + * You should have received a copy of the GNU General Public License
71.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
71.20 + */
71.21 +
71.22 +package org.sonews.storage.impl;
71.23 +
71.24 +import java.sql.Connection;
71.25 +import java.sql.DriverManager;
71.26 +import java.sql.ResultSet;
71.27 +import java.sql.SQLException;
71.28 +import java.sql.Statement;
71.29 +import java.sql.PreparedStatement;
71.30 +import java.util.ArrayList;
71.31 +import java.util.Enumeration;
71.32 +import java.util.List;
71.33 +import java.util.regex.Matcher;
71.34 +import java.util.regex.Pattern;
71.35 +import java.util.regex.PatternSyntaxException;
71.36 +import javax.mail.Header;
71.37 +import javax.mail.internet.InternetAddress;
71.38 +import javax.mail.internet.MimeUtility;
71.39 +import org.sonews.config.Config;
71.40 +import org.sonews.util.Log;
71.41 +import org.sonews.feed.Subscription;
71.42 +import org.sonews.storage.Article;
71.43 +import org.sonews.storage.ArticleHead;
71.44 +import org.sonews.storage.Channel;
71.45 +import org.sonews.storage.Group;
71.46 +import org.sonews.storage.Storage;
71.47 +import org.sonews.storage.StorageBackendException;
71.48 +import org.sonews.util.Pair;
71.49 +
71.50 +/**
71.51 + * JDBCDatabase facade class.
71.52 + * @author Christian Lins
71.53 + * @since sonews/0.5.0
71.54 + */
71.55 +// TODO: Refactor this class to reduce size (e.g. ArticleDatabase GroupDatabase)
71.56 +public class JDBCDatabase implements Storage
71.57 +{
71.58 +
71.59 + public static final int MAX_RESTARTS = 3;
71.60 +
71.61 + private Connection conn = null;
71.62 + private PreparedStatement pstmtAddArticle1 = null;
71.63 + private PreparedStatement pstmtAddArticle2 = null;
71.64 + private PreparedStatement pstmtAddArticle3 = null;
71.65 + private PreparedStatement pstmtAddArticle4 = null;
71.66 + private PreparedStatement pstmtAddGroup0 = null;
71.67 + private PreparedStatement pstmtAddEvent = null;
71.68 + private PreparedStatement pstmtCountArticles = null;
71.69 + private PreparedStatement pstmtCountGroups = null;
71.70 + private PreparedStatement pstmtDeleteArticle0 = null;
71.71 + private PreparedStatement pstmtDeleteArticle1 = null;
71.72 + private PreparedStatement pstmtDeleteArticle2 = null;
71.73 + private PreparedStatement pstmtDeleteArticle3 = null;
71.74 + private PreparedStatement pstmtGetArticle0 = null;
71.75 + private PreparedStatement pstmtGetArticle1 = null;
71.76 + private PreparedStatement pstmtGetArticleHeaders0 = null;
71.77 + private PreparedStatement pstmtGetArticleHeaders1 = null;
71.78 + private PreparedStatement pstmtGetArticleHeads = null;
71.79 + private PreparedStatement pstmtGetArticleIDs = null;
71.80 + private PreparedStatement pstmtGetArticleIndex = null;
71.81 + private PreparedStatement pstmtGetConfigValue = null;
71.82 + private PreparedStatement pstmtGetEventsCount0 = null;
71.83 + private PreparedStatement pstmtGetEventsCount1 = null;
71.84 + private PreparedStatement pstmtGetGroupForList = null;
71.85 + private PreparedStatement pstmtGetGroup0 = null;
71.86 + private PreparedStatement pstmtGetGroup1 = null;
71.87 + private PreparedStatement pstmtGetFirstArticleNumber = null;
71.88 + private PreparedStatement pstmtGetListForGroup = null;
71.89 + private PreparedStatement pstmtGetLastArticleNumber = null;
71.90 + private PreparedStatement pstmtGetMaxArticleID = null;
71.91 + private PreparedStatement pstmtGetMaxArticleIndex = null;
71.92 + private PreparedStatement pstmtGetOldestArticle = null;
71.93 + private PreparedStatement pstmtGetPostingsCount = null;
71.94 + private PreparedStatement pstmtGetSubscriptions = null;
71.95 + private PreparedStatement pstmtIsArticleExisting = null;
71.96 + private PreparedStatement pstmtIsGroupExisting = null;
71.97 + private PreparedStatement pstmtPurgeGroup0 = null;
71.98 + private PreparedStatement pstmtPurgeGroup1 = null;
71.99 + private PreparedStatement pstmtSetConfigValue0 = null;
71.100 + private PreparedStatement pstmtSetConfigValue1 = null;
71.101 + private PreparedStatement pstmtUpdateGroup = null;
71.102 +
71.103 + /** How many times the database connection was reinitialized */
71.104 + private int restarts = 0;
71.105 +
71.106 + /**
71.107 + * Rises the database: reconnect and recreate all prepared statements.
71.108 + * @throws java.lang.SQLException
71.109 + */
71.110 + protected void arise()
71.111 + throws SQLException
71.112 + {
71.113 + try
71.114 + {
71.115 + // Load database driver
71.116 + Class.forName(
71.117 + Config.inst().get(Config.STORAGE_DBMSDRIVER, "java.lang.Object"));
71.118 +
71.119 + // Establish database connection
71.120 + this.conn = DriverManager.getConnection(
71.121 + Config.inst().get(Config.STORAGE_DATABASE, "<not specified>"),
71.122 + Config.inst().get(Config.STORAGE_USER, "root"),
71.123 + Config.inst().get(Config.STORAGE_PASSWORD, ""));
71.124 +
71.125 + this.conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
71.126 + if(this.conn.getTransactionIsolation() != Connection.TRANSACTION_SERIALIZABLE)
71.127 + {
71.128 + Log.msg("Warning: Database is NOT fully serializable!", false);
71.129 + }
71.130 +
71.131 + // Prepare statements for method addArticle()
71.132 + this.pstmtAddArticle1 = conn.prepareStatement(
71.133 + "INSERT INTO articles (article_id, body) VALUES(?, ?)");
71.134 + this.pstmtAddArticle2 = conn.prepareStatement(
71.135 + "INSERT INTO headers (article_id, header_key, header_value, header_index) " +
71.136 + "VALUES (?, ?, ?, ?)");
71.137 + this.pstmtAddArticle3 = conn.prepareStatement(
71.138 + "INSERT INTO postings (group_id, article_id, article_index)" +
71.139 + "VALUES (?, ?, ?)");
71.140 + this.pstmtAddArticle4 = conn.prepareStatement(
71.141 + "INSERT INTO article_ids (article_id, message_id) VALUES (?, ?)");
71.142 +
71.143 + // Prepare statement for method addStatValue()
71.144 + this.pstmtAddEvent = conn.prepareStatement(
71.145 + "INSERT INTO events VALUES (?, ?, ?)");
71.146 +
71.147 + // Prepare statement for method addGroup()
71.148 + this.pstmtAddGroup0 = conn.prepareStatement(
71.149 + "INSERT INTO groups (name, flags) VALUES (?, ?)");
71.150 +
71.151 + // Prepare statement for method countArticles()
71.152 + this.pstmtCountArticles = conn.prepareStatement(
71.153 + "SELECT Count(article_id) FROM article_ids");
71.154 +
71.155 + // Prepare statement for method countGroups()
71.156 + this.pstmtCountGroups = conn.prepareStatement(
71.157 + "SELECT Count(group_id) FROM groups WHERE " +
71.158 + "flags & " + Channel.DELETED + " = 0");
71.159 +
71.160 + // Prepare statements for method delete(article)
71.161 + this.pstmtDeleteArticle0 = conn.prepareStatement(
71.162 + "DELETE FROM articles WHERE article_id = " +
71.163 + "(SELECT article_id FROM article_ids WHERE message_id = ?)");
71.164 + this.pstmtDeleteArticle1 = conn.prepareStatement(
71.165 + "DELETE FROM headers WHERE article_id = " +
71.166 + "(SELECT article_id FROM article_ids WHERE message_id = ?)");
71.167 + this.pstmtDeleteArticle2 = conn.prepareStatement(
71.168 + "DELETE FROM postings WHERE article_id = " +
71.169 + "(SELECT article_id FROM article_ids WHERE message_id = ?)");
71.170 + this.pstmtDeleteArticle3 = conn.prepareStatement(
71.171 + "DELETE FROM article_ids WHERE message_id = ?");
71.172 +
71.173 + // Prepare statements for methods getArticle()
71.174 + this.pstmtGetArticle0 = conn.prepareStatement(
71.175 + "SELECT * FROM articles WHERE article_id = " +
71.176 + "(SELECT article_id FROM article_ids WHERE message_id = ?)");
71.177 + this.pstmtGetArticle1 = conn.prepareStatement(
71.178 + "SELECT * FROM articles WHERE article_id = " +
71.179 + "(SELECT article_id FROM postings WHERE " +
71.180 + "article_index = ? AND group_id = ?)");
71.181 +
71.182 + // Prepare statement for method getArticleHeaders()
71.183 + this.pstmtGetArticleHeaders0 = conn.prepareStatement(
71.184 + "SELECT header_key, header_value FROM headers WHERE article_id = ? " +
71.185 + "ORDER BY header_index ASC");
71.186 +
71.187 + // Prepare statement for method getArticleHeaders(regular expr pattern)
71.188 + this.pstmtGetArticleHeaders1 = conn.prepareStatement(
71.189 + "SELECT p.article_index, h.header_value FROM headers h " +
71.190 + "INNER JOIN postings p ON h.article_id = p.article_id " +
71.191 + "INNER JOIN groups g ON p.group_id = g.group_id " +
71.192 + "WHERE g.name = ? AND " +
71.193 + "h.header_key = ? AND " +
71.194 + "p.article_index >= ? " +
71.195 + "ORDER BY p.article_index ASC");
71.196 +
71.197 + this.pstmtGetArticleIDs = conn.prepareStatement(
71.198 + "SELECT article_index FROM postings WHERE group_id = ?");
71.199 +
71.200 + // Prepare statement for method getArticleIndex
71.201 + this.pstmtGetArticleIndex = conn.prepareStatement(
71.202 + "SELECT article_index FROM postings WHERE " +
71.203 + "article_id = (SELECT article_id FROM article_ids " +
71.204 + "WHERE message_id = ?) " +
71.205 + " AND group_id = ?");
71.206 +
71.207 + // Prepare statements for method getArticleHeads()
71.208 + this.pstmtGetArticleHeads = conn.prepareStatement(
71.209 + "SELECT article_id, article_index FROM postings WHERE " +
71.210 + "postings.group_id = ? AND article_index >= ? AND " +
71.211 + "article_index <= ?");
71.212 +
71.213 + // Prepare statements for method getConfigValue()
71.214 + this.pstmtGetConfigValue = conn.prepareStatement(
71.215 + "SELECT config_value FROM config WHERE config_key = ?");
71.216 +
71.217 + // Prepare statements for method getEventsCount()
71.218 + this.pstmtGetEventsCount0 = conn.prepareStatement(
71.219 + "SELECT Count(*) FROM events WHERE event_key = ? AND " +
71.220 + "event_time >= ? AND event_time < ?");
71.221 +
71.222 + this.pstmtGetEventsCount1 = conn.prepareStatement(
71.223 + "SELECT Count(*) FROM events WHERE event_key = ? AND " +
71.224 + "event_time >= ? AND event_time < ? AND group_id = ?");
71.225 +
71.226 + // Prepare statement for method getGroupForList()
71.227 + this.pstmtGetGroupForList = conn.prepareStatement(
71.228 + "SELECT name FROM groups INNER JOIN groups2list " +
71.229 + "ON groups.group_id = groups2list.group_id " +
71.230 + "WHERE groups2list.listaddress = ?");
71.231 +
71.232 + // Prepare statement for method getGroup()
71.233 + this.pstmtGetGroup0 = conn.prepareStatement(
71.234 + "SELECT group_id, flags FROM groups WHERE Name = ?");
71.235 + this.pstmtGetGroup1 = conn.prepareStatement(
71.236 + "SELECT name FROM groups WHERE group_id = ?");
71.237 +
71.238 + // Prepare statement for method getLastArticleNumber()
71.239 + this.pstmtGetLastArticleNumber = conn.prepareStatement(
71.240 + "SELECT Max(article_index) FROM postings WHERE group_id = ?");
71.241 +
71.242 + // Prepare statement for method getListForGroup()
71.243 + this.pstmtGetListForGroup = conn.prepareStatement(
71.244 + "SELECT listaddress FROM groups2list INNER JOIN groups " +
71.245 + "ON groups.group_id = groups2list.group_id WHERE name = ?");
71.246 +
71.247 + // Prepare statement for method getMaxArticleID()
71.248 + this.pstmtGetMaxArticleID = conn.prepareStatement(
71.249 + "SELECT Max(article_id) FROM articles");
71.250 +
71.251 + // Prepare statement for method getMaxArticleIndex()
71.252 + this.pstmtGetMaxArticleIndex = conn.prepareStatement(
71.253 + "SELECT Max(article_index) FROM postings WHERE group_id = ?");
71.254 +
71.255 + // Prepare statement for method getOldestArticle()
71.256 + this.pstmtGetOldestArticle = conn.prepareStatement(
71.257 + "SELECT message_id FROM article_ids WHERE article_id = " +
71.258 + "(SELECT Min(article_id) FROM article_ids)");
71.259 +
71.260 + // Prepare statement for method getFirstArticleNumber()
71.261 + this.pstmtGetFirstArticleNumber = conn.prepareStatement(
71.262 + "SELECT Min(article_index) FROM postings WHERE group_id = ?");
71.263 +
71.264 + // Prepare statement for method getPostingsCount()
71.265 + this.pstmtGetPostingsCount = conn.prepareStatement(
71.266 + "SELECT Count(*) FROM postings NATURAL JOIN groups " +
71.267 + "WHERE groups.name = ?");
71.268 +
71.269 + // Prepare statement for method getSubscriptions()
71.270 + this.pstmtGetSubscriptions = conn.prepareStatement(
71.271 + "SELECT host, port, name FROM peers NATURAL JOIN " +
71.272 + "peer_subscriptions NATURAL JOIN groups WHERE feedtype = ?");
71.273 +
71.274 + // Prepare statement for method isArticleExisting()
71.275 + this.pstmtIsArticleExisting = conn.prepareStatement(
71.276 + "SELECT Count(article_id) FROM article_ids WHERE message_id = ?");
71.277 +
71.278 + // Prepare statement for method isGroupExisting()
71.279 + this.pstmtIsGroupExisting = conn.prepareStatement(
71.280 + "SELECT * FROM groups WHERE name = ?");
71.281 +
71.282 + // Prepare statement for method setConfigValue()
71.283 + this.pstmtSetConfigValue0 = conn.prepareStatement(
71.284 + "DELETE FROM config WHERE config_key = ?");
71.285 + this.pstmtSetConfigValue1 = conn.prepareStatement(
71.286 + "INSERT INTO config VALUES(?, ?)");
71.287 +
71.288 + // Prepare statements for method purgeGroup()
71.289 + this.pstmtPurgeGroup0 = conn.prepareStatement(
71.290 + "DELETE FROM peer_subscriptions WHERE group_id = ?");
71.291 + this.pstmtPurgeGroup1 = conn.prepareStatement(
71.292 + "DELETE FROM groups WHERE group_id = ?");
71.293 +
71.294 + // Prepare statement for method update(Group)
71.295 + this.pstmtUpdateGroup = conn.prepareStatement(
71.296 + "UPDATE groups SET flags = ?, name = ? WHERE group_id = ?");
71.297 + }
71.298 + catch(ClassNotFoundException ex)
71.299 + {
71.300 + throw new Error("JDBC Driver not found!", ex);
71.301 + }
71.302 + }
71.303 +
71.304 + /**
71.305 + * Adds an article to the database.
71.306 + * @param article
71.307 + * @return
71.308 + * @throws java.sql.SQLException
71.309 + */
71.310 + @Override
71.311 + public void addArticle(final Article article)
71.312 + throws StorageBackendException
71.313 + {
71.314 + try
71.315 + {
71.316 + this.conn.setAutoCommit(false);
71.317 +
71.318 + int newArticleID = getMaxArticleID() + 1;
71.319 +
71.320 + // Fill prepared statement with values;
71.321 + // writes body to article table
71.322 + pstmtAddArticle1.setInt(1, newArticleID);
71.323 + pstmtAddArticle1.setBytes(2, article.getBody());
71.324 + pstmtAddArticle1.execute();
71.325 +
71.326 + // Add headers
71.327 + Enumeration headers = article.getAllHeaders();
71.328 + for(int n = 0; headers.hasMoreElements(); n++)
71.329 + {
71.330 + Header header = (Header)headers.nextElement();
71.331 + pstmtAddArticle2.setInt(1, newArticleID);
71.332 + pstmtAddArticle2.setString(2, header.getName().toLowerCase());
71.333 + pstmtAddArticle2.setString(3,
71.334 + header.getValue().replaceAll("[\r\n]", ""));
71.335 + pstmtAddArticle2.setInt(4, n);
71.336 + pstmtAddArticle2.execute();
71.337 + }
71.338 +
71.339 + // For each newsgroup add a reference
71.340 + List<Group> groups = article.getGroups();
71.341 + for(Group group : groups)
71.342 + {
71.343 + pstmtAddArticle3.setLong(1, group.getInternalID());
71.344 + pstmtAddArticle3.setInt(2, newArticleID);
71.345 + pstmtAddArticle3.setLong(3, getMaxArticleIndex(group.getInternalID()) + 1);
71.346 + pstmtAddArticle3.execute();
71.347 + }
71.348 +
71.349 + // Write message-id to article_ids table
71.350 + this.pstmtAddArticle4.setInt(1, newArticleID);
71.351 + this.pstmtAddArticle4.setString(2, article.getMessageID());
71.352 + this.pstmtAddArticle4.execute();
71.353 +
71.354 + this.conn.commit();
71.355 + this.conn.setAutoCommit(true);
71.356 +
71.357 + this.restarts = 0; // Reset error count
71.358 + }
71.359 + catch(SQLException ex)
71.360 + {
71.361 + try
71.362 + {
71.363 + this.conn.rollback(); // Rollback changes
71.364 + }
71.365 + catch(SQLException ex2)
71.366 + {
71.367 + Log.msg("Rollback of addArticle() failed: " + ex2, false);
71.368 + }
71.369 +
71.370 + try
71.371 + {
71.372 + this.conn.setAutoCommit(true); // and release locks
71.373 + }
71.374 + catch(SQLException ex2)
71.375 + {
71.376 + Log.msg("setAutoCommit(true) of addArticle() failed: " + ex2, false);
71.377 + }
71.378 +
71.379 + restartConnection(ex);
71.380 + addArticle(article);
71.381 + }
71.382 + }
71.383 +
71.384 + /**
71.385 + * Adds a group to the JDBCDatabase. This method is not accessible via NNTP.
71.386 + * @param name
71.387 + * @throws java.sql.SQLException
71.388 + */
71.389 + @Override
71.390 + public void addGroup(String name, int flags)
71.391 + throws StorageBackendException
71.392 + {
71.393 + try
71.394 + {
71.395 + this.conn.setAutoCommit(false);
71.396 + pstmtAddGroup0.setString(1, name);
71.397 + pstmtAddGroup0.setInt(2, flags);
71.398 +
71.399 + pstmtAddGroup0.executeUpdate();
71.400 + this.conn.commit();
71.401 + this.conn.setAutoCommit(true);
71.402 + this.restarts = 0; // Reset error count
71.403 + }
71.404 + catch(SQLException ex)
71.405 + {
71.406 + try
71.407 + {
71.408 + this.conn.rollback();
71.409 + this.conn.setAutoCommit(true);
71.410 + }
71.411 + catch(SQLException ex2)
71.412 + {
71.413 + ex2.printStackTrace();
71.414 + }
71.415 +
71.416 + restartConnection(ex);
71.417 + addGroup(name, flags);
71.418 + }
71.419 + }
71.420 +
71.421 + @Override
71.422 + public void addEvent(long time, int type, long gid)
71.423 + throws StorageBackendException
71.424 + {
71.425 + try
71.426 + {
71.427 + this.conn.setAutoCommit(false);
71.428 + this.pstmtAddEvent.setLong(1, time);
71.429 + this.pstmtAddEvent.setInt(2, type);
71.430 + this.pstmtAddEvent.setLong(3, gid);
71.431 + this.pstmtAddEvent.executeUpdate();
71.432 + this.conn.commit();
71.433 + this.conn.setAutoCommit(true);
71.434 + this.restarts = 0;
71.435 + }
71.436 + catch(SQLException ex)
71.437 + {
71.438 + try
71.439 + {
71.440 + this.conn.rollback();
71.441 + this.conn.setAutoCommit(true);
71.442 + }
71.443 + catch(SQLException ex2)
71.444 + {
71.445 + ex2.printStackTrace();
71.446 + }
71.447 +
71.448 + restartConnection(ex);
71.449 + addEvent(time, type, gid);
71.450 + }
71.451 + }
71.452 +
71.453 + @Override
71.454 + public int countArticles()
71.455 + throws StorageBackendException
71.456 + {
71.457 + ResultSet rs = null;
71.458 +
71.459 + try
71.460 + {
71.461 + rs = this.pstmtCountArticles.executeQuery();
71.462 + if(rs.next())
71.463 + {
71.464 + return rs.getInt(1);
71.465 + }
71.466 + else
71.467 + {
71.468 + return -1;
71.469 + }
71.470 + }
71.471 + catch(SQLException ex)
71.472 + {
71.473 + restartConnection(ex);
71.474 + return countArticles();
71.475 + }
71.476 + finally
71.477 + {
71.478 + if(rs != null)
71.479 + {
71.480 + try
71.481 + {
71.482 + rs.close();
71.483 + }
71.484 + catch(SQLException ex)
71.485 + {
71.486 + ex.printStackTrace();
71.487 + }
71.488 + restarts = 0;
71.489 + }
71.490 + }
71.491 + }
71.492 +
71.493 + @Override
71.494 + public int countGroups()
71.495 + throws StorageBackendException
71.496 + {
71.497 + ResultSet rs = null;
71.498 +
71.499 + try
71.500 + {
71.501 + rs = this.pstmtCountGroups.executeQuery();
71.502 + if(rs.next())
71.503 + {
71.504 + return rs.getInt(1);
71.505 + }
71.506 + else
71.507 + {
71.508 + return -1;
71.509 + }
71.510 + }
71.511 + catch(SQLException ex)
71.512 + {
71.513 + restartConnection(ex);
71.514 + return countGroups();
71.515 + }
71.516 + finally
71.517 + {
71.518 + if(rs != null)
71.519 + {
71.520 + try
71.521 + {
71.522 + rs.close();
71.523 + }
71.524 + catch(SQLException ex)
71.525 + {
71.526 + ex.printStackTrace();
71.527 + }
71.528 + restarts = 0;
71.529 + }
71.530 + }
71.531 + }
71.532 +
71.533 + @Override
71.534 + public void delete(final String messageID)
71.535 + throws StorageBackendException
71.536 + {
71.537 + try
71.538 + {
71.539 + this.conn.setAutoCommit(false);
71.540 +
71.541 + this.pstmtDeleteArticle0.setString(1, messageID);
71.542 + int rs = this.pstmtDeleteArticle0.executeUpdate();
71.543 +
71.544 + // We do not trust the ON DELETE CASCADE functionality to delete
71.545 + // orphaned references...
71.546 + this.pstmtDeleteArticle1.setString(1, messageID);
71.547 + rs = this.pstmtDeleteArticle1.executeUpdate();
71.548 +
71.549 + this.pstmtDeleteArticle2.setString(1, messageID);
71.550 + rs = this.pstmtDeleteArticle2.executeUpdate();
71.551 +
71.552 + this.pstmtDeleteArticle3.setString(1, messageID);
71.553 + rs = this.pstmtDeleteArticle3.executeUpdate();
71.554 +
71.555 + this.conn.commit();
71.556 + this.conn.setAutoCommit(true);
71.557 + }
71.558 + catch(SQLException ex)
71.559 + {
71.560 + throw new StorageBackendException(ex);
71.561 + }
71.562 + }
71.563 +
71.564 + @Override
71.565 + public Article getArticle(String messageID)
71.566 + throws StorageBackendException
71.567 + {
71.568 + ResultSet rs = null;
71.569 + try
71.570 + {
71.571 + pstmtGetArticle0.setString(1, messageID);
71.572 + rs = pstmtGetArticle0.executeQuery();
71.573 +
71.574 + if(!rs.next())
71.575 + {
71.576 + return null;
71.577 + }
71.578 + else
71.579 + {
71.580 + byte[] body = rs.getBytes("body");
71.581 + String headers = getArticleHeaders(rs.getInt("article_id"));
71.582 + return new Article(headers, body);
71.583 + }
71.584 + }
71.585 + catch(SQLException ex)
71.586 + {
71.587 + restartConnection(ex);
71.588 + return getArticle(messageID);
71.589 + }
71.590 + finally
71.591 + {
71.592 + if(rs != null)
71.593 + {
71.594 + try
71.595 + {
71.596 + rs.close();
71.597 + }
71.598 + catch(SQLException ex)
71.599 + {
71.600 + ex.printStackTrace();
71.601 + }
71.602 + restarts = 0; // Reset error count
71.603 + }
71.604 + }
71.605 + }
71.606 +
71.607 + /**
71.608 + * Retrieves an article by its ID.
71.609 + * @param articleID
71.610 + * @return
71.611 + * @throws StorageBackendException
71.612 + */
71.613 + @Override
71.614 + public Article getArticle(long articleIndex, long gid)
71.615 + throws StorageBackendException
71.616 + {
71.617 + ResultSet rs = null;
71.618 +
71.619 + try
71.620 + {
71.621 + this.pstmtGetArticle1.setLong(1, articleIndex);
71.622 + this.pstmtGetArticle1.setLong(2, gid);
71.623 +
71.624 + rs = this.pstmtGetArticle1.executeQuery();
71.625 +
71.626 + if(rs.next())
71.627 + {
71.628 + byte[] body = rs.getBytes("body");
71.629 + String headers = getArticleHeaders(rs.getInt("article_id"));
71.630 + return new Article(headers, body);
71.631 + }
71.632 + else
71.633 + {
71.634 + return null;
71.635 + }
71.636 + }
71.637 + catch(SQLException ex)
71.638 + {
71.639 + restartConnection(ex);
71.640 + return getArticle(articleIndex, gid);
71.641 + }
71.642 + finally
71.643 + {
71.644 + if(rs != null)
71.645 + {
71.646 + try
71.647 + {
71.648 + rs.close();
71.649 + }
71.650 + catch(SQLException ex)
71.651 + {
71.652 + ex.printStackTrace();
71.653 + }
71.654 + restarts = 0;
71.655 + }
71.656 + }
71.657 + }
71.658 +
71.659 + /**
71.660 + * Searches for fitting header values using the given regular expression.
71.661 + * @param group
71.662 + * @param start
71.663 + * @param end
71.664 + * @param headerKey
71.665 + * @param pattern
71.666 + * @return
71.667 + * @throws StorageBackendException
71.668 + */
71.669 + @Override
71.670 + public List<Pair<Long, String>> getArticleHeaders(Channel group, long start,
71.671 + long end, String headerKey, String patStr)
71.672 + throws StorageBackendException, PatternSyntaxException
71.673 + {
71.674 + ResultSet rs = null;
71.675 + List<Pair<Long, String>> heads = new ArrayList<Pair<Long, String>>();
71.676 +
71.677 + try
71.678 + {
71.679 + this.pstmtGetArticleHeaders1.setString(1, group.getName());
71.680 + this.pstmtGetArticleHeaders1.setString(2, headerKey);
71.681 + this.pstmtGetArticleHeaders1.setLong(3, start);
71.682 +
71.683 + rs = this.pstmtGetArticleHeaders1.executeQuery();
71.684 +
71.685 + // Convert the "NNTP" regex to Java regex
71.686 + patStr = patStr.replace("*", ".*");
71.687 + Pattern pattern = Pattern.compile(patStr);
71.688 +
71.689 + while(rs.next())
71.690 + {
71.691 + Long articleIndex = rs.getLong(1);
71.692 + if(end < 0 || articleIndex <= end) // Match start is done via SQL
71.693 + {
71.694 + String headerValue = rs.getString(2);
71.695 + Matcher matcher = pattern.matcher(headerValue);
71.696 + if(matcher.matches())
71.697 + {
71.698 + heads.add(new Pair<Long, String>(articleIndex, headerValue));
71.699 + }
71.700 + }
71.701 + }
71.702 + }
71.703 + catch(SQLException ex)
71.704 + {
71.705 + restartConnection(ex);
71.706 + return getArticleHeaders(group, start, end, headerKey, patStr);
71.707 + }
71.708 + finally
71.709 + {
71.710 + if(rs != null)
71.711 + {
71.712 + try
71.713 + {
71.714 + rs.close();
71.715 + }
71.716 + catch(SQLException ex)
71.717 + {
71.718 + ex.printStackTrace();
71.719 + }
71.720 + }
71.721 + }
71.722 +
71.723 + return heads;
71.724 + }
71.725 +
71.726 + private String getArticleHeaders(long articleID)
71.727 + throws StorageBackendException
71.728 + {
71.729 + ResultSet rs = null;
71.730 +
71.731 + try
71.732 + {
71.733 + this.pstmtGetArticleHeaders0.setLong(1, articleID);
71.734 + rs = this.pstmtGetArticleHeaders0.executeQuery();
71.735 +
71.736 + StringBuilder buf = new StringBuilder();
71.737 + if(rs.next())
71.738 + {
71.739 + for(;;)
71.740 + {
71.741 + buf.append(rs.getString(1)); // key
71.742 + buf.append(": ");
71.743 + String foldedValue = MimeUtility.fold(0, rs.getString(2));
71.744 + buf.append(foldedValue); // value
71.745 + if(rs.next())
71.746 + {
71.747 + buf.append("\r\n");
71.748 + }
71.749 + else
71.750 + {
71.751 + break;
71.752 + }
71.753 + }
71.754 + }
71.755 +
71.756 + return buf.toString();
71.757 + }
71.758 + catch(SQLException ex)
71.759 + {
71.760 + restartConnection(ex);
71.761 + return getArticleHeaders(articleID);
71.762 + }
71.763 + finally
71.764 + {
71.765 + if(rs != null)
71.766 + {
71.767 + try
71.768 + {
71.769 + rs.close();
71.770 + }
71.771 + catch(SQLException ex)
71.772 + {
71.773 + ex.printStackTrace();
71.774 + }
71.775 + }
71.776 + }
71.777 + }
71.778 +
71.779 + @Override
71.780 + public long getArticleIndex(Article article, Group group)
71.781 + throws StorageBackendException
71.782 + {
71.783 + ResultSet rs = null;
71.784 +
71.785 + try
71.786 + {
71.787 + this.pstmtGetArticleIndex.setString(1, article.getMessageID());
71.788 + this.pstmtGetArticleIndex.setLong(2, group.getInternalID());
71.789 +
71.790 + rs = this.pstmtGetArticleIndex.executeQuery();
71.791 + if(rs.next())
71.792 + {
71.793 + return rs.getLong(1);
71.794 + }
71.795 + else
71.796 + {
71.797 + return -1;
71.798 + }
71.799 + }
71.800 + catch(SQLException ex)
71.801 + {
71.802 + restartConnection(ex);
71.803 + return getArticleIndex(article, group);
71.804 + }
71.805 + finally
71.806 + {
71.807 + if(rs != null)
71.808 + {
71.809 + try
71.810 + {
71.811 + rs.close();
71.812 + }
71.813 + catch(SQLException ex)
71.814 + {
71.815 + ex.printStackTrace();
71.816 + }
71.817 + }
71.818 + }
71.819 + }
71.820 +
71.821 + /**
71.822 + * Returns a list of Long/Article Pairs.
71.823 + * @throws java.sql.SQLException
71.824 + */
71.825 + @Override
71.826 + public List<Pair<Long, ArticleHead>> getArticleHeads(Group group, long first,
71.827 + long last)
71.828 + throws StorageBackendException
71.829 + {
71.830 + ResultSet rs = null;
71.831 +
71.832 + try
71.833 + {
71.834 + this.pstmtGetArticleHeads.setLong(1, group.getInternalID());
71.835 + this.pstmtGetArticleHeads.setLong(2, first);
71.836 + this.pstmtGetArticleHeads.setLong(3, last);
71.837 + rs = pstmtGetArticleHeads.executeQuery();
71.838 +
71.839 + List<Pair<Long, ArticleHead>> articles
71.840 + = new ArrayList<Pair<Long, ArticleHead>>();
71.841 +
71.842 + while (rs.next())
71.843 + {
71.844 + long aid = rs.getLong("article_id");
71.845 + long aidx = rs.getLong("article_index");
71.846 + String headers = getArticleHeaders(aid);
71.847 + articles.add(new Pair<Long, ArticleHead>(aidx,
71.848 + new ArticleHead(headers)));
71.849 + }
71.850 +
71.851 + return articles;
71.852 + }
71.853 + catch(SQLException ex)
71.854 + {
71.855 + restartConnection(ex);
71.856 + return getArticleHeads(group, first, last);
71.857 + }
71.858 + finally
71.859 + {
71.860 + if(rs != null)
71.861 + {
71.862 + try
71.863 + {
71.864 + rs.close();
71.865 + }
71.866 + catch(SQLException ex)
71.867 + {
71.868 + ex.printStackTrace();
71.869 + }
71.870 + }
71.871 + }
71.872 + }
71.873 +
71.874 + @Override
71.875 + public List<Long> getArticleNumbers(long gid)
71.876 + throws StorageBackendException
71.877 + {
71.878 + ResultSet rs = null;
71.879 + try
71.880 + {
71.881 + List<Long> ids = new ArrayList<Long>();
71.882 + this.pstmtGetArticleIDs.setLong(1, gid);
71.883 + rs = this.pstmtGetArticleIDs.executeQuery();
71.884 + while(rs.next())
71.885 + {
71.886 + ids.add(rs.getLong(1));
71.887 + }
71.888 + return ids;
71.889 + }
71.890 + catch(SQLException ex)
71.891 + {
71.892 + restartConnection(ex);
71.893 + return getArticleNumbers(gid);
71.894 + }
71.895 + finally
71.896 + {
71.897 + if(rs != null)
71.898 + {
71.899 + try
71.900 + {
71.901 + rs.close();
71.902 + restarts = 0; // Clear the restart count after successful request
71.903 + }
71.904 + catch(SQLException ex)
71.905 + {
71.906 + ex.printStackTrace();
71.907 + }
71.908 + }
71.909 + }
71.910 + }
71.911 +
71.912 + @Override
71.913 + public String getConfigValue(String key)
71.914 + throws StorageBackendException
71.915 + {
71.916 + ResultSet rs = null;
71.917 + try
71.918 + {
71.919 + this.pstmtGetConfigValue.setString(1, key);
71.920 +
71.921 + rs = this.pstmtGetConfigValue.executeQuery();
71.922 + if(rs.next())
71.923 + {
71.924 + return rs.getString(1); // First data on index 1 not 0
71.925 + }
71.926 + else
71.927 + {
71.928 + return null;
71.929 + }
71.930 + }
71.931 + catch(SQLException ex)
71.932 + {
71.933 + restartConnection(ex);
71.934 + return getConfigValue(key);
71.935 + }
71.936 + finally
71.937 + {
71.938 + if(rs != null)
71.939 + {
71.940 + try
71.941 + {
71.942 + rs.close();
71.943 + }
71.944 + catch(SQLException ex)
71.945 + {
71.946 + ex.printStackTrace();
71.947 + }
71.948 + restarts = 0; // Clear the restart count after successful request
71.949 + }
71.950 + }
71.951 + }
71.952 +
71.953 + @Override
71.954 + public int getEventsCount(int type, long start, long end, Channel channel)
71.955 + throws StorageBackendException
71.956 + {
71.957 + ResultSet rs = null;
71.958 +
71.959 + try
71.960 + {
71.961 + if(channel == null)
71.962 + {
71.963 + this.pstmtGetEventsCount0.setInt(1, type);
71.964 + this.pstmtGetEventsCount0.setLong(2, start);
71.965 + this.pstmtGetEventsCount0.setLong(3, end);
71.966 + rs = this.pstmtGetEventsCount0.executeQuery();
71.967 + }
71.968 + else
71.969 + {
71.970 + this.pstmtGetEventsCount1.setInt(1, type);
71.971 + this.pstmtGetEventsCount1.setLong(2, start);
71.972 + this.pstmtGetEventsCount1.setLong(3, end);
71.973 + this.pstmtGetEventsCount1.setLong(4, channel.getInternalID());
71.974 + rs = this.pstmtGetEventsCount1.executeQuery();
71.975 + }
71.976 +
71.977 + if(rs.next())
71.978 + {
71.979 + return rs.getInt(1);
71.980 + }
71.981 + else
71.982 + {
71.983 + return -1;
71.984 + }
71.985 + }
71.986 + catch(SQLException ex)
71.987 + {
71.988 + restartConnection(ex);
71.989 + return getEventsCount(type, start, end, channel);
71.990 + }
71.991 + finally
71.992 + {
71.993 + if(rs != null)
71.994 + {
71.995 + try
71.996 + {
71.997 + rs.close();
71.998 + }
71.999 + catch(SQLException ex)
71.1000 + {
71.1001 + ex.printStackTrace();
71.1002 + }
71.1003 + }
71.1004 + }
71.1005 + }
71.1006 +
71.1007 + /**
71.1008 + * Reads all Groups from the JDBCDatabase.
71.1009 + * @return
71.1010 + * @throws StorageBackendException
71.1011 + */
71.1012 + @Override
71.1013 + public List<Channel> getGroups()
71.1014 + throws StorageBackendException
71.1015 + {
71.1016 + ResultSet rs;
71.1017 + List<Channel> buffer = new ArrayList<Channel>();
71.1018 + Statement stmt = null;
71.1019 +
71.1020 + try
71.1021 + {
71.1022 + stmt = conn.createStatement();
71.1023 + rs = stmt.executeQuery("SELECT * FROM groups ORDER BY name");
71.1024 +
71.1025 + while(rs.next())
71.1026 + {
71.1027 + String name = rs.getString("name");
71.1028 + long id = rs.getLong("group_id");
71.1029 + int flags = rs.getInt("flags");
71.1030 +
71.1031 + Group group = new Group(name, id, flags);
71.1032 + buffer.add(group);
71.1033 + }
71.1034 +
71.1035 + return buffer;
71.1036 + }
71.1037 + catch(SQLException ex)
71.1038 + {
71.1039 + restartConnection(ex);
71.1040 + return getGroups();
71.1041 + }
71.1042 + finally
71.1043 + {
71.1044 + if(stmt != null)
71.1045 + {
71.1046 + try
71.1047 + {
71.1048 + stmt.close(); // Implicitely closes ResultSets
71.1049 + }
71.1050 + catch(SQLException ex)
71.1051 + {
71.1052 + ex.printStackTrace();
71.1053 + }
71.1054 + }
71.1055 + }
71.1056 + }
71.1057 +
71.1058 + @Override
71.1059 + public List<String> getGroupsForList(InternetAddress listAddress)
71.1060 + throws StorageBackendException
71.1061 + {
71.1062 + ResultSet rs = null;
71.1063 +
71.1064 + try
71.1065 + {
71.1066 + this.pstmtGetGroupForList.setString(1, listAddress.getAddress());
71.1067 +
71.1068 + rs = this.pstmtGetGroupForList.executeQuery();
71.1069 + List<String> groups = new ArrayList<String>();
71.1070 + while(rs.next())
71.1071 + {
71.1072 + String group = rs.getString(1);
71.1073 + groups.add(group);
71.1074 + }
71.1075 + return groups;
71.1076 + }
71.1077 + catch(SQLException ex)
71.1078 + {
71.1079 + restartConnection(ex);
71.1080 + return getGroupsForList(listAddress);
71.1081 + }
71.1082 + finally
71.1083 + {
71.1084 + if(rs != null)
71.1085 + {
71.1086 + try
71.1087 + {
71.1088 + rs.close();
71.1089 + }
71.1090 + catch(SQLException ex)
71.1091 + {
71.1092 + ex.printStackTrace();
71.1093 + }
71.1094 + }
71.1095 + }
71.1096 + }
71.1097 +
71.1098 + /**
71.1099 + * Returns the Group that is identified by the name.
71.1100 + * @param name
71.1101 + * @return
71.1102 + * @throws StorageBackendException
71.1103 + */
71.1104 + @Override
71.1105 + public Group getGroup(String name)
71.1106 + throws StorageBackendException
71.1107 + {
71.1108 + ResultSet rs = null;
71.1109 +
71.1110 + try
71.1111 + {
71.1112 + this.pstmtGetGroup0.setString(1, name);
71.1113 + rs = this.pstmtGetGroup0.executeQuery();
71.1114 +
71.1115 + if (!rs.next())
71.1116 + {
71.1117 + return null;
71.1118 + }
71.1119 + else
71.1120 + {
71.1121 + long id = rs.getLong("group_id");
71.1122 + int flags = rs.getInt("flags");
71.1123 + return new Group(name, id, flags);
71.1124 + }
71.1125 + }
71.1126 + catch(SQLException ex)
71.1127 + {
71.1128 + restartConnection(ex);
71.1129 + return getGroup(name);
71.1130 + }
71.1131 + finally
71.1132 + {
71.1133 + if(rs != null)
71.1134 + {
71.1135 + try
71.1136 + {
71.1137 + rs.close();
71.1138 + }
71.1139 + catch(SQLException ex)
71.1140 + {
71.1141 + ex.printStackTrace();
71.1142 + }
71.1143 + }
71.1144 + }
71.1145 + }
71.1146 +
71.1147 + @Override
71.1148 + public String getListForGroup(String group)
71.1149 + throws StorageBackendException
71.1150 + {
71.1151 + ResultSet rs = null;
71.1152 +
71.1153 + try
71.1154 + {
71.1155 + this.pstmtGetListForGroup.setString(1, group);
71.1156 + rs = this.pstmtGetListForGroup.executeQuery();
71.1157 + if (rs.next())
71.1158 + {
71.1159 + return rs.getString(1);
71.1160 + }
71.1161 + else
71.1162 + {
71.1163 + return null;
71.1164 + }
71.1165 + }
71.1166 + catch(SQLException ex)
71.1167 + {
71.1168 + restartConnection(ex);
71.1169 + return getListForGroup(group);
71.1170 + }
71.1171 + finally
71.1172 + {
71.1173 + if(rs != null)
71.1174 + {
71.1175 + try
71.1176 + {
71.1177 + rs.close();
71.1178 + }
71.1179 + catch(SQLException ex)
71.1180 + {
71.1181 + ex.printStackTrace();
71.1182 + }
71.1183 + }
71.1184 + }
71.1185 + }
71.1186 +
71.1187 + private int getMaxArticleIndex(long groupID)
71.1188 + throws StorageBackendException
71.1189 + {
71.1190 + ResultSet rs = null;
71.1191 +
71.1192 + try
71.1193 + {
71.1194 + this.pstmtGetMaxArticleIndex.setLong(1, groupID);
71.1195 + rs = this.pstmtGetMaxArticleIndex.executeQuery();
71.1196 +
71.1197 + int maxIndex = 0;
71.1198 + if (rs.next())
71.1199 + {
71.1200 + maxIndex = rs.getInt(1);
71.1201 + }
71.1202 +
71.1203 + return maxIndex;
71.1204 + }
71.1205 + catch(SQLException ex)
71.1206 + {
71.1207 + restartConnection(ex);
71.1208 + return getMaxArticleIndex(groupID);
71.1209 + }
71.1210 + finally
71.1211 + {
71.1212 + if(rs != null)
71.1213 + {
71.1214 + try
71.1215 + {
71.1216 + rs.close();
71.1217 + }
71.1218 + catch(SQLException ex)
71.1219 + {
71.1220 + ex.printStackTrace();
71.1221 + }
71.1222 + }
71.1223 + }
71.1224 + }
71.1225 +
71.1226 + private int getMaxArticleID()
71.1227 + throws StorageBackendException
71.1228 + {
71.1229 + ResultSet rs = null;
71.1230 +
71.1231 + try
71.1232 + {
71.1233 + rs = this.pstmtGetMaxArticleID.executeQuery();
71.1234 +
71.1235 + int maxIndex = 0;
71.1236 + if (rs.next())
71.1237 + {
71.1238 + maxIndex = rs.getInt(1);
71.1239 + }
71.1240 +
71.1241 + return maxIndex;
71.1242 + }
71.1243 + catch(SQLException ex)
71.1244 + {
71.1245 + restartConnection(ex);
71.1246 + return getMaxArticleID();
71.1247 + }
71.1248 + finally
71.1249 + {
71.1250 + if(rs != null)
71.1251 + {
71.1252 + try
71.1253 + {
71.1254 + rs.close();
71.1255 + }
71.1256 + catch(SQLException ex)
71.1257 + {
71.1258 + ex.printStackTrace();
71.1259 + }
71.1260 + }
71.1261 + }
71.1262 + }
71.1263 +
71.1264 + @Override
71.1265 + public int getLastArticleNumber(Group group)
71.1266 + throws StorageBackendException
71.1267 + {
71.1268 + ResultSet rs = null;
71.1269 +
71.1270 + try
71.1271 + {
71.1272 + this.pstmtGetLastArticleNumber.setLong(1, group.getInternalID());
71.1273 + rs = this.pstmtGetLastArticleNumber.executeQuery();
71.1274 + if (rs.next())
71.1275 + {
71.1276 + return rs.getInt(1);
71.1277 + }
71.1278 + else
71.1279 + {
71.1280 + return 0;
71.1281 + }
71.1282 + }
71.1283 + catch(SQLException ex)
71.1284 + {
71.1285 + restartConnection(ex);
71.1286 + return getLastArticleNumber(group);
71.1287 + }
71.1288 + finally
71.1289 + {
71.1290 + if(rs != null)
71.1291 + {
71.1292 + try
71.1293 + {
71.1294 + rs.close();
71.1295 + }
71.1296 + catch(SQLException ex)
71.1297 + {
71.1298 + ex.printStackTrace();
71.1299 + }
71.1300 + }
71.1301 + }
71.1302 + }
71.1303 +
71.1304 + @Override
71.1305 + public int getFirstArticleNumber(Group group)
71.1306 + throws StorageBackendException
71.1307 + {
71.1308 + ResultSet rs = null;
71.1309 + try
71.1310 + {
71.1311 + this.pstmtGetFirstArticleNumber.setLong(1, group.getInternalID());
71.1312 + rs = this.pstmtGetFirstArticleNumber.executeQuery();
71.1313 + if(rs.next())
71.1314 + {
71.1315 + return rs.getInt(1);
71.1316 + }
71.1317 + else
71.1318 + {
71.1319 + return 0;
71.1320 + }
71.1321 + }
71.1322 + catch(SQLException ex)
71.1323 + {
71.1324 + restartConnection(ex);
71.1325 + return getFirstArticleNumber(group);
71.1326 + }
71.1327 + finally
71.1328 + {
71.1329 + if(rs != null)
71.1330 + {
71.1331 + try
71.1332 + {
71.1333 + rs.close();
71.1334 + }
71.1335 + catch(SQLException ex)
71.1336 + {
71.1337 + ex.printStackTrace();
71.1338 + }
71.1339 + }
71.1340 + }
71.1341 + }
71.1342 +
71.1343 + /**
71.1344 + * Returns a group name identified by the given id.
71.1345 + * @param id
71.1346 + * @return
71.1347 + * @throws StorageBackendException
71.1348 + */
71.1349 + public String getGroup(int id)
71.1350 + throws StorageBackendException
71.1351 + {
71.1352 + ResultSet rs = null;
71.1353 +
71.1354 + try
71.1355 + {
71.1356 + this.pstmtGetGroup1.setInt(1, id);
71.1357 + rs = this.pstmtGetGroup1.executeQuery();
71.1358 +
71.1359 + if (rs.next())
71.1360 + {
71.1361 + return rs.getString(1);
71.1362 + }
71.1363 + else
71.1364 + {
71.1365 + return null;
71.1366 + }
71.1367 + }
71.1368 + catch(SQLException ex)
71.1369 + {
71.1370 + restartConnection(ex);
71.1371 + return getGroup(id);
71.1372 + }
71.1373 + finally
71.1374 + {
71.1375 + if(rs != null)
71.1376 + {
71.1377 + try
71.1378 + {
71.1379 + rs.close();
71.1380 + }
71.1381 + catch(SQLException ex)
71.1382 + {
71.1383 + ex.printStackTrace();
71.1384 + }
71.1385 + }
71.1386 + }
71.1387 + }
71.1388 +
71.1389 + @Override
71.1390 + public double getEventsPerHour(int key, long gid)
71.1391 + throws StorageBackendException
71.1392 + {
71.1393 + String gidquery = "";
71.1394 + if(gid >= 0)
71.1395 + {
71.1396 + gidquery = " AND group_id = " + gid;
71.1397 + }
71.1398 +
71.1399 + Statement stmt = null;
71.1400 + ResultSet rs = null;
71.1401 +
71.1402 + try
71.1403 + {
71.1404 + stmt = this.conn.createStatement();
71.1405 + rs = stmt.executeQuery("SELECT Count(*) / (Max(event_time) - Min(event_time))" +
71.1406 + " * 1000 * 60 * 60 FROM events WHERE event_key = " + key + gidquery);
71.1407 +
71.1408 + if(rs.next())
71.1409 + {
71.1410 + restarts = 0; // reset error count
71.1411 + return rs.getDouble(1);
71.1412 + }
71.1413 + else
71.1414 + {
71.1415 + return Double.NaN;
71.1416 + }
71.1417 + }
71.1418 + catch(SQLException ex)
71.1419 + {
71.1420 + restartConnection(ex);
71.1421 + return getEventsPerHour(key, gid);
71.1422 + }
71.1423 + finally
71.1424 + {
71.1425 + try
71.1426 + {
71.1427 + if(stmt != null)
71.1428 + {
71.1429 + stmt.close(); // Implicitely closes the result sets
71.1430 + }
71.1431 + }
71.1432 + catch(SQLException ex)
71.1433 + {
71.1434 + ex.printStackTrace();
71.1435 + }
71.1436 + }
71.1437 + }
71.1438 +
71.1439 + @Override
71.1440 + public String getOldestArticle()
71.1441 + throws StorageBackendException
71.1442 + {
71.1443 + ResultSet rs = null;
71.1444 +
71.1445 + try
71.1446 + {
71.1447 + rs = this.pstmtGetOldestArticle.executeQuery();
71.1448 + if(rs.next())
71.1449 + {
71.1450 + return rs.getString(1);
71.1451 + }
71.1452 + else
71.1453 + {
71.1454 + return null;
71.1455 + }
71.1456 + }
71.1457 + catch(SQLException ex)
71.1458 + {
71.1459 + restartConnection(ex);
71.1460 + return getOldestArticle();
71.1461 + }
71.1462 + finally
71.1463 + {
71.1464 + if(rs != null)
71.1465 + {
71.1466 + try
71.1467 + {
71.1468 + rs.close();
71.1469 + }
71.1470 + catch(SQLException ex)
71.1471 + {
71.1472 + ex.printStackTrace();
71.1473 + }
71.1474 + }
71.1475 + }
71.1476 + }
71.1477 +
71.1478 + @Override
71.1479 + public int getPostingsCount(String groupname)
71.1480 + throws StorageBackendException
71.1481 + {
71.1482 + ResultSet rs = null;
71.1483 +
71.1484 + try
71.1485 + {
71.1486 + this.pstmtGetPostingsCount.setString(1, groupname);
71.1487 + rs = this.pstmtGetPostingsCount.executeQuery();
71.1488 + if(rs.next())
71.1489 + {
71.1490 + return rs.getInt(1);
71.1491 + }
71.1492 + else
71.1493 + {
71.1494 + Log.msg("Warning: Count on postings return nothing!", true);
71.1495 + return 0;
71.1496 + }
71.1497 + }
71.1498 + catch(SQLException ex)
71.1499 + {
71.1500 + restartConnection(ex);
71.1501 + return getPostingsCount(groupname);
71.1502 + }
71.1503 + finally
71.1504 + {
71.1505 + if(rs != null)
71.1506 + {
71.1507 + try
71.1508 + {
71.1509 + rs.close();
71.1510 + }
71.1511 + catch(SQLException ex)
71.1512 + {
71.1513 + ex.printStackTrace();
71.1514 + }
71.1515 + }
71.1516 + }
71.1517 + }
71.1518 +
71.1519 + @Override
71.1520 + public List<Subscription> getSubscriptions(int feedtype)
71.1521 + throws StorageBackendException
71.1522 + {
71.1523 + ResultSet rs = null;
71.1524 +
71.1525 + try
71.1526 + {
71.1527 + List<Subscription> subs = new ArrayList<Subscription>();
71.1528 + this.pstmtGetSubscriptions.setInt(1, feedtype);
71.1529 + rs = this.pstmtGetSubscriptions.executeQuery();
71.1530 +
71.1531 + while(rs.next())
71.1532 + {
71.1533 + String host = rs.getString("host");
71.1534 + String group = rs.getString("name");
71.1535 + int port = rs.getInt("port");
71.1536 + subs.add(new Subscription(host, port, feedtype, group));
71.1537 + }
71.1538 +
71.1539 + return subs;
71.1540 + }
71.1541 + catch(SQLException ex)
71.1542 + {
71.1543 + restartConnection(ex);
71.1544 + return getSubscriptions(feedtype);
71.1545 + }
71.1546 + finally
71.1547 + {
71.1548 + if(rs != null)
71.1549 + {
71.1550 + try
71.1551 + {
71.1552 + rs.close();
71.1553 + }
71.1554 + catch(SQLException ex)
71.1555 + {
71.1556 + ex.printStackTrace();
71.1557 + }
71.1558 + }
71.1559 + }
71.1560 + }
71.1561 +
71.1562 + /**
71.1563 + * Checks if there is an article with the given messageid in the JDBCDatabase.
71.1564 + * @param name
71.1565 + * @return
71.1566 + * @throws StorageBackendException
71.1567 + */
71.1568 + @Override
71.1569 + public boolean isArticleExisting(String messageID)
71.1570 + throws StorageBackendException
71.1571 + {
71.1572 + ResultSet rs = null;
71.1573 +
71.1574 + try
71.1575 + {
71.1576 + this.pstmtIsArticleExisting.setString(1, messageID);
71.1577 + rs = this.pstmtIsArticleExisting.executeQuery();
71.1578 + return rs.next() && rs.getInt(1) == 1;
71.1579 + }
71.1580 + catch(SQLException ex)
71.1581 + {
71.1582 + restartConnection(ex);
71.1583 + return isArticleExisting(messageID);
71.1584 + }
71.1585 + finally
71.1586 + {
71.1587 + if(rs != null)
71.1588 + {
71.1589 + try
71.1590 + {
71.1591 + rs.close();
71.1592 + }
71.1593 + catch(SQLException ex)
71.1594 + {
71.1595 + ex.printStackTrace();
71.1596 + }
71.1597 + }
71.1598 + }
71.1599 + }
71.1600 +
71.1601 + /**
71.1602 + * Checks if there is a group with the given name in the JDBCDatabase.
71.1603 + * @param name
71.1604 + * @return
71.1605 + * @throws StorageBackendException
71.1606 + */
71.1607 + @Override
71.1608 + public boolean isGroupExisting(String name)
71.1609 + throws StorageBackendException
71.1610 + {
71.1611 + ResultSet rs = null;
71.1612 +
71.1613 + try
71.1614 + {
71.1615 + this.pstmtIsGroupExisting.setString(1, name);
71.1616 + rs = this.pstmtIsGroupExisting.executeQuery();
71.1617 + return rs.next();
71.1618 + }
71.1619 + catch(SQLException ex)
71.1620 + {
71.1621 + restartConnection(ex);
71.1622 + return isGroupExisting(name);
71.1623 + }
71.1624 + finally
71.1625 + {
71.1626 + if(rs != null)
71.1627 + {
71.1628 + try
71.1629 + {
71.1630 + rs.close();
71.1631 + }
71.1632 + catch(SQLException ex)
71.1633 + {
71.1634 + ex.printStackTrace();
71.1635 + }
71.1636 + }
71.1637 + }
71.1638 + }
71.1639 +
71.1640 + @Override
71.1641 + public void setConfigValue(String key, String value)
71.1642 + throws StorageBackendException
71.1643 + {
71.1644 + try
71.1645 + {
71.1646 + conn.setAutoCommit(false);
71.1647 + this.pstmtSetConfigValue0.setString(1, key);
71.1648 + this.pstmtSetConfigValue0.execute();
71.1649 + this.pstmtSetConfigValue1.setString(1, key);
71.1650 + this.pstmtSetConfigValue1.setString(2, value);
71.1651 + this.pstmtSetConfigValue1.execute();
71.1652 + conn.commit();
71.1653 + conn.setAutoCommit(true);
71.1654 + }
71.1655 + catch(SQLException ex)
71.1656 + {
71.1657 + restartConnection(ex);
71.1658 + setConfigValue(key, value);
71.1659 + }
71.1660 + }
71.1661 +
71.1662 + /**
71.1663 + * Closes the JDBCDatabase connection.
71.1664 + */
71.1665 + public void shutdown()
71.1666 + throws StorageBackendException
71.1667 + {
71.1668 + try
71.1669 + {
71.1670 + if(this.conn != null)
71.1671 + {
71.1672 + this.conn.close();
71.1673 + }
71.1674 + }
71.1675 + catch(SQLException ex)
71.1676 + {
71.1677 + throw new StorageBackendException(ex);
71.1678 + }
71.1679 + }
71.1680 +
71.1681 + @Override
71.1682 + public void purgeGroup(Group group)
71.1683 + throws StorageBackendException
71.1684 + {
71.1685 + try
71.1686 + {
71.1687 + this.pstmtPurgeGroup0.setLong(1, group.getInternalID());
71.1688 + this.pstmtPurgeGroup0.executeUpdate();
71.1689 +
71.1690 + this.pstmtPurgeGroup1.setLong(1, group.getInternalID());
71.1691 + this.pstmtPurgeGroup1.executeUpdate();
71.1692 + }
71.1693 + catch(SQLException ex)
71.1694 + {
71.1695 + restartConnection(ex);
71.1696 + purgeGroup(group);
71.1697 + }
71.1698 + }
71.1699 +
71.1700 + private void restartConnection(SQLException cause)
71.1701 + throws StorageBackendException
71.1702 + {
71.1703 + restarts++;
71.1704 + Log.msg(Thread.currentThread()
71.1705 + + ": Database connection was closed (restart " + restarts + ").", false);
71.1706 +
71.1707 + if(restarts >= MAX_RESTARTS)
71.1708 + {
71.1709 + // Delete the current, probably broken JDBCDatabase instance.
71.1710 + // So no one can use the instance any more.
71.1711 + JDBCDatabaseProvider.instances.remove(Thread.currentThread());
71.1712 +
71.1713 + // Throw the exception upwards
71.1714 + throw new StorageBackendException(cause);
71.1715 + }
71.1716 +
71.1717 + try
71.1718 + {
71.1719 + Thread.sleep(1500L * restarts);
71.1720 + }
71.1721 + catch(InterruptedException ex)
71.1722 + {
71.1723 + Log.msg("Interrupted: " + ex.getMessage(), false);
71.1724 + }
71.1725 +
71.1726 + // Try to properly close the old database connection
71.1727 + try
71.1728 + {
71.1729 + if(this.conn != null)
71.1730 + {
71.1731 + this.conn.close();
71.1732 + }
71.1733 + }
71.1734 + catch(SQLException ex)
71.1735 + {
71.1736 + Log.msg(ex.getMessage(), true);
71.1737 + }
71.1738 +
71.1739 + try
71.1740 + {
71.1741 + // Try to reinitialize database connection
71.1742 + arise();
71.1743 + }
71.1744 + catch(SQLException ex)
71.1745 + {
71.1746 + Log.msg(ex.getMessage(), true);
71.1747 + restartConnection(ex);
71.1748 + }
71.1749 + }
71.1750 +
71.1751 + /**
71.1752 + * Writes the flags and the name of the given group to the database.
71.1753 + * @param group
71.1754 + * @throws StorageBackendException
71.1755 + */
71.1756 + @Override
71.1757 + public boolean update(Group group)
71.1758 + throws StorageBackendException
71.1759 + {
71.1760 + try
71.1761 + {
71.1762 + this.pstmtUpdateGroup.setInt(1, group.getFlags());
71.1763 + this.pstmtUpdateGroup.setString(2, group.getName());
71.1764 + this.pstmtUpdateGroup.setLong(3, group.getInternalID());
71.1765 + int rs = this.pstmtUpdateGroup.executeUpdate();
71.1766 + return rs == 1;
71.1767 + }
71.1768 + catch(SQLException ex)
71.1769 + {
71.1770 + restartConnection(ex);
71.1771 + return update(group);
71.1772 + }
71.1773 + }
71.1774 +
71.1775 +}
72.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
72.2 +++ b/org/sonews/storage/impl/JDBCDatabaseProvider.java Wed Jul 22 14:04:05 2009 +0200
72.3 @@ -0,0 +1,67 @@
72.4 +/*
72.5 + * SONEWS News Server
72.6 + * see AUTHORS for the list of contributors
72.7 + *
72.8 + * This program is free software: you can redistribute it and/or modify
72.9 + * it under the terms of the GNU General Public License as published by
72.10 + * the Free Software Foundation, either version 3 of the License, or
72.11 + * (at your option) any later version.
72.12 + *
72.13 + * This program is distributed in the hope that it will be useful,
72.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
72.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
72.16 + * GNU General Public License for more details.
72.17 + *
72.18 + * You should have received a copy of the GNU General Public License
72.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
72.20 + */
72.21 +
72.22 +package org.sonews.storage.impl;
72.23 +
72.24 +import org.sonews.storage.*;
72.25 +import java.sql.SQLException;
72.26 +import java.util.Map;
72.27 +import java.util.concurrent.ConcurrentHashMap;
72.28 +
72.29 +/**
72.30 + *
72.31 + * @author Christian Lins
72.32 + * @since sonews/1.0
72.33 + */
72.34 +public class JDBCDatabaseProvider implements StorageProvider
72.35 +{
72.36 +
72.37 + protected static final Map<Thread, JDBCDatabase> instances
72.38 + = new ConcurrentHashMap<Thread, JDBCDatabase>();
72.39 +
72.40 + @Override
72.41 + public boolean isSupported(String uri)
72.42 + {
72.43 + throw new UnsupportedOperationException("Not supported yet.");
72.44 + }
72.45 +
72.46 + @Override
72.47 + public Storage storage(Thread thread)
72.48 + throws StorageBackendException
72.49 + {
72.50 + try
72.51 + {
72.52 + if(!instances.containsKey(Thread.currentThread()))
72.53 + {
72.54 + JDBCDatabase db = new JDBCDatabase();
72.55 + db.arise();
72.56 + instances.put(Thread.currentThread(), db);
72.57 + return db;
72.58 + }
72.59 + else
72.60 + {
72.61 + return instances.get(Thread.currentThread());
72.62 + }
72.63 + }
72.64 + catch(SQLException ex)
72.65 + {
72.66 + throw new StorageBackendException(ex);
72.67 + }
72.68 + }
72.69 +
72.70 +}
73.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
73.2 +++ b/org/sonews/storage/package.html Wed Jul 22 14:04:05 2009 +0200
73.3 @@ -0,0 +1,2 @@
73.4 +Contains classes of the storage backend and the Group and Article
73.5 +abstraction.
73.6 \ No newline at end of file
74.1 --- a/org/sonews/util/AbstractConfig.java Wed Jul 01 10:48:22 2009 +0200
74.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
74.3 @@ -1,43 +0,0 @@
74.4 -/*
74.5 - * SONEWS News Server
74.6 - * see AUTHORS for the list of contributors
74.7 - *
74.8 - * This program is free software: you can redistribute it and/or modify
74.9 - * it under the terms of the GNU General Public License as published by
74.10 - * the Free Software Foundation, either version 3 of the License, or
74.11 - * (at your option) any later version.
74.12 - *
74.13 - * This program is distributed in the hope that it will be useful,
74.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
74.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
74.16 - * GNU General Public License for more details.
74.17 - *
74.18 - * You should have received a copy of the GNU General Public License
74.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
74.20 - */
74.21 -
74.22 -package org.sonews.util;
74.23 -
74.24 -/**
74.25 - * Base class for Config and BootstrapConfig.
74.26 - * @author Christian Lins
74.27 - * @since sonews/0.5.0
74.28 - */
74.29 -public abstract class AbstractConfig
74.30 -{
74.31 -
74.32 - public abstract String get(String key, String defVal);
74.33 -
74.34 - public int get(final String key, final int defVal)
74.35 - {
74.36 - return Integer.parseInt(
74.37 - get(key, Integer.toString(defVal)));
74.38 - }
74.39 -
74.40 - public boolean get(String key, boolean defVal)
74.41 - {
74.42 - String val = get(key, Boolean.toString(defVal));
74.43 - return Boolean.parseBoolean(val);
74.44 - }
74.45 -
74.46 -}
75.1 --- a/org/sonews/util/DatabaseSetup.java Wed Jul 01 10:48:22 2009 +0200
75.2 +++ b/org/sonews/util/DatabaseSetup.java Wed Jul 22 14:04:05 2009 +0200
75.3 @@ -25,7 +25,7 @@
75.4 import java.sql.Statement;
75.5 import java.util.HashMap;
75.6 import java.util.Map;
75.7 -import org.sonews.daemon.BootstrapConfig;
75.8 +import org.sonews.config.Config;
75.9 import org.sonews.util.io.Resource;
75.10
75.11 /**
75.12 @@ -108,7 +108,9 @@
75.13 for(String chunk : tmplChunks)
75.14 {
75.15 if(chunk.trim().equals(""))
75.16 + {
75.17 continue;
75.18 + }
75.19
75.20 Statement stmt = conn.createStatement();
75.21 stmt.execute(chunk);
75.22 @@ -117,12 +119,7 @@
75.23 conn.commit();
75.24 conn.setAutoCommit(true);
75.25
75.26 - BootstrapConfig config = BootstrapConfig.getInstance();
75.27 - config.set(BootstrapConfig.STORAGE_DATABASE, url);
75.28 - config.set(BootstrapConfig.STORAGE_DBMSDRIVER, driverMap.get(dbmsType));
75.29 - config.set(BootstrapConfig.STORAGE_PASSWORD, dbPassword);
75.30 - config.set(BootstrapConfig.STORAGE_USER, dbUser);
75.31 - config.save();
75.32 + // Create config file
75.33
75.34 System.out.println("Ok");
75.35 }
76.1 --- a/org/sonews/util/Log.java Wed Jul 01 10:48:22 2009 +0200
76.2 +++ b/org/sonews/util/Log.java Wed Jul 22 14:04:05 2009 +0200
76.3 @@ -18,8 +18,8 @@
76.4
76.5 package org.sonews.util;
76.6
76.7 -import org.sonews.daemon.*;
76.8 import java.util.Date;
76.9 +import org.sonews.config.Config;
76.10
76.11 /**
76.12 * Provides logging and debugging methods.
76.13 @@ -31,10 +31,10 @@
76.14
76.15 public static boolean isDebug()
76.16 {
76.17 - // We must use BootstrapConfig here otherwise we come
76.18 + // We must use FileConfig here otherwise we come
76.19 // into hell's kittchen when using the Logger within the
76.20 // Database class.
76.21 - return BootstrapConfig.getInstance().get(BootstrapConfig.DEBUG, false);
76.22 + return Config.inst().get(Config.DEBUG, false);
76.23 }
76.24
76.25 /**
77.1 --- a/org/sonews/util/Purger.java Wed Jul 01 10:48:22 2009 +0200
77.2 +++ b/org/sonews/util/Purger.java Wed Jul 22 14:04:05 2009 +0200
77.3 @@ -18,70 +18,131 @@
77.4
77.5 package org.sonews.util;
77.6
77.7 -import org.sonews.daemon.Config;
77.8 -import org.sonews.daemon.storage.Database;
77.9 -import org.sonews.daemon.storage.Article;
77.10 +import org.sonews.daemon.AbstractDaemon;
77.11 +import org.sonews.config.Config;
77.12 +import org.sonews.storage.Article;
77.13 +import org.sonews.storage.Headers;
77.14 import java.util.Date;
77.15 +import java.util.List;
77.16 +import org.sonews.storage.Channel;
77.17 +import org.sonews.storage.Group;
77.18 +import org.sonews.storage.StorageBackendException;
77.19 +import org.sonews.storage.StorageManager;
77.20
77.21 /**
77.22 * The purger is started in configurable intervals to search
77.23 - * for old messages that can be purged.
77.24 + * for messages that can be purged. A message must be deleted if its lifetime
77.25 + * has exceeded, if it was marked as deleted or if the maximum number of
77.26 + * articles in the database is reached.
77.27 * @author Christian Lins
77.28 * @since sonews/0.5.0
77.29 */
77.30 -public class Purger
77.31 +public class Purger extends AbstractDaemon
77.32 {
77.33
77.34 - private long lifetime;
77.35 -
77.36 - public Purger()
77.37 - {
77.38 - this.lifetime = Config.getInstance().get("sonews.article.lifetime", 30)
77.39 - * 24L * 60L * 60L * 1000L; // in Milliseconds
77.40 - }
77.41 -
77.42 /**
77.43 * Loops through all messages and deletes them if their time
77.44 * has come.
77.45 */
77.46 - void purge()
77.47 - throws Exception
77.48 + @Override
77.49 + public void run()
77.50 {
77.51 - System.out.println("Purging old messages...");
77.52 + try
77.53 + {
77.54 + while(isRunning())
77.55 + {
77.56 + purgeDeleted();
77.57 + purgeOutdated();
77.58
77.59 - for (;;)
77.60 + Thread.sleep(120000); // Sleep for two minutes
77.61 + }
77.62 + }
77.63 + catch(StorageBackendException ex)
77.64 {
77.65 - // TODO: Delete articles directly in database
77.66 - Article art = null; //Database.getInstance().getOldestArticle();
77.67 - if (art == null) // No articles in the database
77.68 + ex.printStackTrace();
77.69 + }
77.70 + catch(InterruptedException ex)
77.71 + {
77.72 + Log.msg("Purger interrupted: " + ex, true);
77.73 + }
77.74 + }
77.75 +
77.76 + private void purgeDeleted()
77.77 + throws StorageBackendException
77.78 + {
77.79 + List<Channel> groups = StorageManager.current().getGroups();
77.80 + for(Channel channel : groups)
77.81 + {
77.82 + if(!(channel instanceof Group))
77.83 + continue;
77.84 +
77.85 + Group group = (Group)channel;
77.86 + // Look for groups that are marked as deleted
77.87 + if(group.isDeleted())
77.88 {
77.89 - break;
77.90 + List<Long> ids = StorageManager.current().getArticleNumbers(group.getInternalID());
77.91 + if(ids.size() == 0)
77.92 + {
77.93 + StorageManager.current().purgeGroup(group);
77.94 + Log.msg("Group " + group.getName() + " purged.", true);
77.95 + }
77.96 +
77.97 + for(int n = 0; n < ids.size() && n < 10; n++)
77.98 + {
77.99 + Article art = StorageManager.current().getArticle(ids.get(n), group.getInternalID());
77.100 + StorageManager.current().delete(art.getMessageID());
77.101 + Log.msg("Article " + art.getMessageID() + " purged.", true);
77.102 + }
77.103 + }
77.104 + }
77.105 + }
77.106 +
77.107 + private void purgeOutdated()
77.108 + throws InterruptedException, StorageBackendException
77.109 + {
77.110 + long articleMaximum =
77.111 + Config.inst().get("sonews.article.maxnum", Long.MAX_VALUE);
77.112 + long lifetime =
77.113 + Config.inst().get("sonews.article.lifetime", -1);
77.114 +
77.115 + if(lifetime > 0 || articleMaximum < Stats.getInstance().getNumberOfNews())
77.116 + {
77.117 + Log.msg("Purging old messages...", true);
77.118 + String mid = StorageManager.current().getOldestArticle();
77.119 + if (mid == null) // No articles in the database
77.120 + {
77.121 + return;
77.122 }
77.123
77.124 -/* if (art.getDate().getTime() < (new Date().getTime() + this.lifetime))
77.125 + Article art = StorageManager.current().getArticle(mid);
77.126 + long artDate = 0;
77.127 + String dateStr = art.getHeader(Headers.DATE)[0];
77.128 + try
77.129 {
77.130 - // Database.getInstance().delete(art);
77.131 - System.out.println("Deleted: " + art);
77.132 + artDate = Date.parse(dateStr) / 1000 / 60 / 60 / 24;
77.133 + }
77.134 + catch (IllegalArgumentException ex)
77.135 + {
77.136 + Log.msg("Could not parse date string: " + dateStr + " " + ex, true);
77.137 + }
77.138 +
77.139 + // Should we delete the message because of its age or because the
77.140 + // article maximum was reached?
77.141 + if (lifetime < 0 || artDate < (new Date().getTime() + lifetime))
77.142 + {
77.143 + StorageManager.current().delete(mid);
77.144 + System.out.println("Deleted: " + mid);
77.145 }
77.146 else
77.147 {
77.148 - break;
77.149 - }*/
77.150 + Thread.sleep(1000 * 60); // Wait 60 seconds
77.151 + return;
77.152 + }
77.153 }
77.154 - }
77.155 -
77.156 - public static void main(String[] args)
77.157 - {
77.158 - try
77.159 + else
77.160 {
77.161 - Purger purger = new Purger();
77.162 - purger.purge();
77.163 - System.exit(0);
77.164 - }
77.165 - catch(Exception ex)
77.166 - {
77.167 - ex.printStackTrace();
77.168 - System.exit(1);
77.169 + Log.msg("Lifetime purger is disabled", true);
77.170 + Thread.sleep(1000 * 60 * 30); // Wait 30 minutes
77.171 }
77.172 }
77.173
78.1 --- a/org/sonews/util/Stats.java Wed Jul 01 10:48:22 2009 +0200
78.2 +++ b/org/sonews/util/Stats.java Wed Jul 22 14:04:05 2009 +0200
78.3 @@ -18,10 +18,11 @@
78.4
78.5 package org.sonews.util;
78.6
78.7 -import java.sql.SQLException;
78.8 import java.util.Calendar;
78.9 -import org.sonews.daemon.storage.Database;
78.10 -import org.sonews.daemon.storage.Group;
78.11 +import org.sonews.config.Config;
78.12 +import org.sonews.storage.Channel;
78.13 +import org.sonews.storage.StorageBackendException;
78.14 +import org.sonews.storage.StorageManager;
78.15
78.16 /**
78.17 * Class that capsulates statistical data gathering.
78.18 @@ -48,26 +49,36 @@
78.19 private Stats() {}
78.20
78.21 private volatile int connectedClients = 0;
78.22 -
78.23 +
78.24 + /**
78.25 + * A generic method that writes event data to the storage backend.
78.26 + * If event logging is disabled with sonews.eventlog=false this method
78.27 + * simply does nothing.
78.28 + * @param type
78.29 + * @param groupname
78.30 + */
78.31 private void addEvent(byte type, String groupname)
78.32 {
78.33 - Group group = Group.getByName(groupname);
78.34 - if(group != null)
78.35 + if(Config.inst().get(Config.EVENTLOG, true))
78.36 {
78.37 - try
78.38 + Channel group = Channel.getByName(groupname);
78.39 + if(group != null)
78.40 {
78.41 - Database.getInstance().addEvent(
78.42 - System.currentTimeMillis(), type, group.getID());
78.43 + try
78.44 + {
78.45 + StorageManager.current().addEvent(
78.46 + System.currentTimeMillis(), type, group.getInternalID());
78.47 + }
78.48 + catch(StorageBackendException ex)
78.49 + {
78.50 + ex.printStackTrace();
78.51 + }
78.52 }
78.53 - catch(SQLException ex)
78.54 + else
78.55 {
78.56 - ex.printStackTrace();
78.57 + Log.msg("Group " + groupname + " does not exist.", true);
78.58 }
78.59 }
78.60 - else
78.61 - {
78.62 - Log.msg("Group " + groupname + " does not exist.", true);
78.63 - }
78.64 }
78.65
78.66 public void clientConnect()
78.67 @@ -89,9 +100,9 @@
78.68 {
78.69 try
78.70 {
78.71 - return Database.getInstance().countGroups();
78.72 + return StorageManager.current().countGroups();
78.73 }
78.74 - catch(SQLException ex)
78.75 + catch(StorageBackendException ex)
78.76 {
78.77 ex.printStackTrace();
78.78 return -1;
78.79 @@ -102,9 +113,9 @@
78.80 {
78.81 try
78.82 {
78.83 - return Database.getInstance().countArticles();
78.84 + return StorageManager.current().countArticles();
78.85 }
78.86 - catch(SQLException ex)
78.87 + catch(StorageBackendException ex)
78.88 {
78.89 ex.printStackTrace();
78.90 return -1;
78.91 @@ -112,7 +123,7 @@
78.92 }
78.93
78.94 public int getYesterdaysEvents(final byte eventType, final int hour,
78.95 - final Group group)
78.96 + final Channel group)
78.97 {
78.98 // Determine the timestamp values for yesterday and the given hour
78.99 Calendar cal = Calendar.getInstance();
78.100 @@ -128,10 +139,10 @@
78.101
78.102 try
78.103 {
78.104 - return Database.getInstance()
78.105 + return StorageManager.current()
78.106 .getEventsCount(eventType, startTimestamp, endTimestamp, group);
78.107 }
78.108 - catch(SQLException ex)
78.109 + catch(StorageBackendException ex)
78.110 {
78.111 ex.printStackTrace();
78.112 return -1;
78.113 @@ -167,9 +178,9 @@
78.114 {
78.115 try
78.116 {
78.117 - return Database.getInstance().getNumberOfEventsPerHour(key, gid);
78.118 + return StorageManager.current().getEventsPerHour(key, gid);
78.119 }
78.120 - catch(SQLException ex)
78.121 + catch(StorageBackendException ex)
78.122 {
78.123 ex.printStackTrace();
78.124 return -1;
79.1 --- a/org/sonews/util/io/ArticleInputStream.java Wed Jul 01 10:48:22 2009 +0200
79.2 +++ b/org/sonews/util/io/ArticleInputStream.java Wed Jul 22 14:04:05 2009 +0200
79.3 @@ -20,9 +20,9 @@
79.4
79.5 import java.io.ByteArrayOutputStream;
79.6 import java.io.IOException;
79.7 -import org.sonews.daemon.storage.*;
79.8 import java.io.InputStream;
79.9 import java.io.UnsupportedEncodingException;
79.10 +import org.sonews.storage.Article;
79.11
79.12 /**
79.13 * Capsulates an Article to provide a raw InputStream.
79.14 @@ -41,11 +41,12 @@
79.15 final ByteArrayOutputStream out = new ByteArrayOutputStream();
79.16 out.write(art.getHeaderSource().getBytes("UTF-8"));
79.17 out.write("\r\n\r\n".getBytes());
79.18 - out.write(art.getBody().getBytes(art.getBodyCharset()));
79.19 + out.write(art.getBody()); // Without CRLF
79.20 out.flush();
79.21 this.buffer = out.toByteArray();
79.22 }
79.23 -
79.24 +
79.25 + @Override
79.26 public int read()
79.27 {
79.28 if(offset >= buffer.length)
80.1 --- a/org/sonews/util/io/ArticleReader.java Wed Jul 01 10:48:22 2009 +0200
80.2 +++ b/org/sonews/util/io/ArticleReader.java Wed Jul 22 14:04:05 2009 +0200
80.3 @@ -26,6 +26,7 @@
80.4 import java.io.UnsupportedEncodingException;
80.5 import java.net.Socket;
80.6 import java.net.UnknownHostException;
80.7 +import org.sonews.config.Config;
80.8 import org.sonews.util.Log;
80.9
80.10 /**
80.11 @@ -72,6 +73,8 @@
80.12 public byte[] getArticleData()
80.13 throws IOException, UnsupportedEncodingException
80.14 {
80.15 + long maxSize = Config.inst().get(Config.ARTICLE_MAXSIZE, 1024) * 1024L;
80.16 +
80.17 try
80.18 {
80.19 this.out.write(("ARTICLE " + this.messageID + "\r\n").getBytes("UTF-8"));
80.20 @@ -90,6 +93,11 @@
80.21 }
80.22
80.23 buf.write(10);
80.24 + if(buf.size() > maxSize)
80.25 + {
80.26 + Log.msg("Skipping message that is too large: " + buf.size(), false);
80.27 + return null;
80.28 + }
80.29 }
80.30
80.31 return buf.toByteArray();
81.1 --- a/org/sonews/util/io/ArticleWriter.java Wed Jul 01 10:48:22 2009 +0200
81.2 +++ b/org/sonews/util/io/ArticleWriter.java Wed Jul 22 14:04:05 2009 +0200
81.3 @@ -25,7 +25,7 @@
81.4 import java.io.UnsupportedEncodingException;
81.5 import java.net.Socket;
81.6 import java.net.UnknownHostException;
81.7 -import org.sonews.daemon.storage.Article;
81.8 +import org.sonews.storage.Article;
81.9
81.10 /**
81.11 * Posts an Article to a NNTP server using the POST command.
82.1 --- a/org/sonews/util/io/VarCharsetReader.java Wed Jul 01 10:48:22 2009 +0200
82.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
82.3 @@ -1,90 +0,0 @@
82.4 -/*
82.5 - * SONEWS News Server
82.6 - * see AUTHORS for the list of contributors
82.7 - *
82.8 - * This program is free software: you can redistribute it and/or modify
82.9 - * it under the terms of the GNU General Public License as published by
82.10 - * the Free Software Foundation, either version 3 of the License, or
82.11 - * (at your option) any later version.
82.12 - *
82.13 - * This program is distributed in the hope that it will be useful,
82.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
82.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
82.16 - * GNU General Public License for more details.
82.17 - *
82.18 - * You should have received a copy of the GNU General Public License
82.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
82.20 - */
82.21 -
82.22 -package org.sonews.util.io;
82.23 -
82.24 -import java.io.IOException;
82.25 -import java.io.InputStream;
82.26 -import java.nio.ByteBuffer;
82.27 -import java.nio.charset.Charset;
82.28 -
82.29 -/**
82.30 - * InputStream that can change its decoding charset while reading from the
82.31 - * underlying byte based stream.
82.32 - * @author Christian Lins
82.33 - * @since sonews/0.5.0
82.34 - */
82.35 -public class VarCharsetReader
82.36 -{
82.37 -
82.38 - private final ByteBuffer buf = ByteBuffer.allocate(4096);
82.39 - private InputStream in;
82.40 -
82.41 - public VarCharsetReader(final InputStream in)
82.42 - {
82.43 - if(in == null)
82.44 - {
82.45 - throw new IllegalArgumentException("null InputStream");
82.46 - }
82.47 - this.in = in;
82.48 - }
82.49 -
82.50 - /**
82.51 - * Reads up to the next newline character and returns the line as String.
82.52 - * The String is decoded using the given charset.
82.53 - */
82.54 - public String readLine(Charset charset)
82.55 - throws IOException
82.56 - {
82.57 - byte[] byteBuf = new byte[1];
82.58 - String bufStr;
82.59 -
82.60 - for(;;)
82.61 - {
82.62 - int read = this.in.read(byteBuf);
82.63 - if(read == 0)
82.64 - {
82.65 - continue;
82.66 - }
82.67 - else if(read == -1)
82.68 - {
82.69 - this.in = null;
82.70 - bufStr = new String(this.buf.array(), 0, this.buf.position(), charset);
82.71 - break;
82.72 - }
82.73 - else if(byteBuf[0] == 10) // Is this safe? \n
82.74 - {
82.75 - bufStr = new String(this.buf.array(), 0, this.buf.position(), charset);
82.76 - break;
82.77 - }
82.78 - else if(byteBuf[0] == 13) // \r
82.79 - { // Skip
82.80 - continue;
82.81 - }
82.82 - else
82.83 - {
82.84 - this.buf.put(byteBuf[0]);
82.85 - }
82.86 - }
82.87 -
82.88 - this.buf.clear();
82.89 -
82.90 - return bufStr;
82.91 - }
82.92 -
82.93 -}
83.1 --- a/org/sonews/web/AbstractSonewsServlet.java Wed Jul 01 10:48:22 2009 +0200
83.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
83.3 @@ -1,113 +0,0 @@
83.4 -/*
83.5 - * SONEWS News Server
83.6 - * see AUTHORS for the list of contributors
83.7 - *
83.8 - * This program is free software: you can redistribute it and/or modify
83.9 - * it under the terms of the GNU General Public License as published by
83.10 - * the Free Software Foundation, either version 3 of the License, or
83.11 - * (at your option) any later version.
83.12 - *
83.13 - * This program is distributed in the hope that it will be useful,
83.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
83.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
83.16 - * GNU General Public License for more details.
83.17 - *
83.18 - * You should have received a copy of the GNU General Public License
83.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
83.20 - */
83.21 -
83.22 -package org.sonews.web;
83.23 -
83.24 -import java.io.BufferedReader;
83.25 -import java.io.IOException;
83.26 -import java.io.InputStreamReader;
83.27 -import java.io.PrintWriter;
83.28 -import java.net.Socket;
83.29 -import javax.servlet.http.HttpServlet;
83.30 -import org.sonews.util.StringTemplate;
83.31 -import org.sonews.util.io.Resource;
83.32 -
83.33 -/**
83.34 - * Base class for all sonews servlets.
83.35 - * @author Christian Lins
83.36 - * @since sonews/0.5.0
83.37 - */
83.38 -public class AbstractSonewsServlet extends HttpServlet
83.39 -{
83.40 -
83.41 - public static final String TemplateRoot = "org/sonews/web/tmpl/";
83.42 -
83.43 - protected String hello = null;
83.44 -
83.45 - private BufferedReader in = null;
83.46 - private PrintWriter out = null;
83.47 - private Socket socket = null;
83.48 -
83.49 - protected void connectToNewsserver()
83.50 - throws IOException
83.51 - {
83.52 - // Get sonews port from properties
83.53 - String port = System.getProperty("sonews.port", "9119");
83.54 - String host = System.getProperty("sonews.host", "localhost");
83.55 -
83.56 - try
83.57 - {
83.58 - this.socket = new Socket(host, Integer.parseInt(port));
83.59 -
83.60 - this.in = new BufferedReader(
83.61 - new InputStreamReader(socket.getInputStream()));
83.62 - this.out = new PrintWriter(socket.getOutputStream());
83.63 -
83.64 - hello = in.readLine(); // Read hello message
83.65 - }
83.66 - catch(IOException ex)
83.67 - {
83.68 - System.out.println("sonews.host=" + host);
83.69 - System.out.println("sonews.port=" + port);
83.70 - System.out.flush();
83.71 - throw ex;
83.72 - }
83.73 - }
83.74 -
83.75 - protected void disconnectFromNewsserver()
83.76 - {
83.77 - try
83.78 - {
83.79 - printlnToNewsserver("QUIT");
83.80 - out.close();
83.81 - readlnFromNewsserver(); // Wait for bye message
83.82 - in.close();
83.83 - socket.close();
83.84 - }
83.85 - catch(IOException ex)
83.86 - {
83.87 - ex.printStackTrace();
83.88 - }
83.89 - }
83.90 -
83.91 - protected StringTemplate getTemplate(String res)
83.92 - {
83.93 - StringTemplate tmpl = new StringTemplate(
83.94 - Resource.getAsString(TemplateRoot + "AbstractSonewsServlet.tmpl", true));
83.95 - String content = Resource.getAsString(TemplateRoot + res, true);
83.96 - String stylesheet = System.getProperty("sonews.web.stylesheet", "style.css");
83.97 -
83.98 - tmpl.set("CONTENT", content);
83.99 - tmpl.set("STYLESHEET", stylesheet);
83.100 -
83.101 - return new StringTemplate(tmpl.toString());
83.102 - }
83.103 -
83.104 - protected void printlnToNewsserver(final String line)
83.105 - {
83.106 - this.out.println(line);
83.107 - this.out.flush();
83.108 - }
83.109 -
83.110 - protected String readlnFromNewsserver()
83.111 - throws IOException
83.112 - {
83.113 - return this.in.readLine();
83.114 - }
83.115 -
83.116 -}
84.1 --- a/org/sonews/web/MemoryBitmapChart.java Wed Jul 01 10:48:22 2009 +0200
84.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
84.3 @@ -1,61 +0,0 @@
84.4 -/*
84.5 - * SONEWS News Server
84.6 - * see AUTHORS for the list of contributors
84.7 - *
84.8 - * This program is free software: you can redistribute it and/or modify
84.9 - * it under the terms of the GNU General Public License as published by
84.10 - * the Free Software Foundation, either version 3 of the License, or
84.11 - * (at your option) any later version.
84.12 - *
84.13 - * This program is distributed in the hope that it will be useful,
84.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
84.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
84.16 - * GNU General Public License for more details.
84.17 - *
84.18 - * You should have received a copy of the GNU General Public License
84.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
84.20 - */
84.21 -
84.22 -package org.sonews.web;
84.23 -
84.24 -import info.monitorenter.gui.chart.Chart2D;
84.25 -import info.monitorenter.gui.chart.IAxis.AxisTitle;
84.26 -import java.awt.Color;
84.27 -import java.awt.image.BufferedImage;
84.28 -import java.io.ByteArrayOutputStream;
84.29 -import java.io.IOException;
84.30 -import javax.imageio.ImageIO;
84.31 -
84.32 -/**
84.33 - * A chart rendered to a memory bitmap.
84.34 - * @author Christian Lins
84.35 - * @since sonews/0.5.0
84.36 - */
84.37 -class MemoryBitmapChart extends Chart2D
84.38 -{
84.39 -
84.40 - public MemoryBitmapChart()
84.41 - {
84.42 - setGridColor(Color.LIGHT_GRAY);
84.43 - getAxisX().setPaintGrid(true);
84.44 - getAxisY().setPaintGrid(true);
84.45 - getAxisX().setAxisTitle(new AxisTitle("time of day"));
84.46 - getAxisY().setAxisTitle(new AxisTitle("processed news"));
84.47 - }
84.48 -
84.49 - public String getContentType()
84.50 - {
84.51 - return "image/png";
84.52 - }
84.53 -
84.54 - public byte[] getRawData(final int width, final int height)
84.55 - throws IOException
84.56 - {
84.57 - setSize(width, height);
84.58 - BufferedImage img = snapShot(width, height);
84.59 - ByteArrayOutputStream out = new ByteArrayOutputStream();
84.60 - ImageIO.write(img, "png", out);
84.61 - return out.toByteArray();
84.62 - }
84.63 -
84.64 -}
85.1 --- a/org/sonews/web/SonewsChartServlet.java Wed Jul 01 10:48:22 2009 +0200
85.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
85.3 @@ -1,114 +0,0 @@
85.4 -/*
85.5 - * SONEWS News Server
85.6 - * see AUTHORS for the list of contributors
85.7 - *
85.8 - * This program is free software: you can redistribute it and/or modify
85.9 - * it under the terms of the GNU General Public License as published by
85.10 - * the Free Software Foundation, either version 3 of the License, or
85.11 - * (at your option) any later version.
85.12 - *
85.13 - * This program is distributed in the hope that it will be useful,
85.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
85.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
85.16 - * GNU General Public License for more details.
85.17 - *
85.18 - * You should have received a copy of the GNU General Public License
85.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
85.20 - */
85.21 -
85.22 -package org.sonews.web;
85.23 -
85.24 -import info.monitorenter.gui.chart.ITrace2D;
85.25 -import info.monitorenter.gui.chart.traces.Trace2DSimple;
85.26 -import java.io.IOException;
85.27 -import javax.servlet.http.HttpServletRequest;
85.28 -import javax.servlet.http.HttpServletResponse;
85.29 -
85.30 -/**
85.31 - * Servlet that creates chart images and returns them as raw PNG images.
85.32 - * @author Christian Lins
85.33 - * @since sonews/0.5.0
85.34 - */
85.35 -public class SonewsChartServlet extends AbstractSonewsServlet
85.36 -{
85.37 -
85.38 - private ITrace2D createProcessMails24(String title, String cmd)
85.39 - throws IOException
85.40 - {
85.41 - int[] data = read24Values(cmd);
85.42 - ITrace2D trace = new Trace2DSimple(title);
85.43 - trace.addPoint(0.0, 0.0); // Start
85.44 -
85.45 - for(int n = 0; n < 24; n++)
85.46 - {
85.47 - trace.addPoint(n, data[n]);
85.48 - }
85.49 -
85.50 - return trace;
85.51 - }
85.52 -
85.53 - @Override
85.54 - public void doGet(HttpServletRequest req, HttpServletResponse resp)
85.55 - throws IOException
85.56 - {
85.57 - synchronized(this)
85.58 - {
85.59 - MemoryBitmapChart chart = new MemoryBitmapChart();
85.60 -
85.61 - String name = req.getParameter("name");
85.62 - String group = req.getParameter("group");
85.63 - ITrace2D trace;
85.64 - String cmd = "XDAEMON LOG";
85.65 -
85.66 - if(name.equals("feedednewsyesterday"))
85.67 - {
85.68 - cmd = cmd + " TRANSMITTED_NEWS";
85.69 - cmd = group != null ? cmd + " " + group : cmd;
85.70 - trace = createProcessMails24(
85.71 - "To peers transmitted mails yesterday", cmd);
85.72 - }
85.73 - else if(name.equals("gatewayednewsyesterday"))
85.74 - {
85.75 - cmd = cmd + " GATEWAYED_NEWS";
85.76 - cmd = group != null ? cmd + " " + group : cmd;
85.77 - trace = createProcessMails24(
85.78 - "Gatewayed mails yesterday", cmd);
85.79 - }
85.80 - else
85.81 - {
85.82 - cmd = cmd + " POSTED_NEWS";
85.83 - cmd = group != null ? cmd + " " + group : cmd;
85.84 - trace = createProcessMails24(
85.85 - "Posted mails yesterday", cmd);
85.86 - }
85.87 - chart.addTrace(trace);
85.88 -
85.89 - resp.getOutputStream().write(chart.getRawData(500, 400));
85.90 - resp.setContentType(chart.getContentType());
85.91 - resp.setStatus(HttpServletResponse.SC_OK);
85.92 - }
85.93 - }
85.94 -
85.95 - private int[] read24Values(String command)
85.96 - throws IOException
85.97 - {
85.98 - int[] values = new int[24];
85.99 - connectToNewsserver();
85.100 - printlnToNewsserver(command);
85.101 - String line = readlnFromNewsserver();
85.102 - if(!line.startsWith("200 "))
85.103 - throw new IOException(command + " not supported!");
85.104 -
85.105 - for(int n = 0; n < 24; n++)
85.106 - {
85.107 - line = readlnFromNewsserver();
85.108 - values[n] = Integer.parseInt(line.split(" ")[1]);
85.109 - }
85.110 -
85.111 - line = readlnFromNewsserver(); // "."
85.112 -
85.113 - disconnectFromNewsserver();
85.114 - return values;
85.115 - }
85.116 -
85.117 -}
86.1 --- a/org/sonews/web/SonewsConfigServlet.java Wed Jul 01 10:48:22 2009 +0200
86.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
86.3 @@ -1,239 +0,0 @@
86.4 -/*
86.5 - * SONEWS News Server
86.6 - * see AUTHORS for the list of contributors
86.7 - *
86.8 - * This program is free software: you can redistribute it and/or modify
86.9 - * it under the terms of the GNU General Public License as published by
86.10 - * the Free Software Foundation, either version 3 of the License, or
86.11 - * (at your option) any later version.
86.12 - *
86.13 - * This program is distributed in the hope that it will be useful,
86.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
86.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
86.16 - * GNU General Public License for more details.
86.17 - *
86.18 - * You should have received a copy of the GNU General Public License
86.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
86.20 - */
86.21 -
86.22 -package org.sonews.web;
86.23 -
86.24 -import java.io.IOException;
86.25 -import java.util.ArrayList;
86.26 -import java.util.List;
86.27 -import java.util.Set;
86.28 -import javax.servlet.http.HttpServletRequest;
86.29 -import javax.servlet.http.HttpServletResponse;
86.30 -import org.sonews.util.StringTemplate;
86.31 -import org.sonews.util.io.Resource;
86.32 -
86.33 -/**
86.34 - * Servlet providing a configuration web interface.
86.35 - * @author Christian Lins
86.36 - * @since sonews/0.5.0
86.37 - */
86.38 -public class SonewsConfigServlet extends AbstractSonewsServlet
86.39 -{
86.40 -
86.41 - private static final long serialVersionUID = 2432543253L;
86.42 -
86.43 - @Override
86.44 - public void doGet(HttpServletRequest req, HttpServletResponse resp)
86.45 - throws IOException
86.46 - {
86.47 - synchronized(this)
86.48 - {
86.49 - connectToNewsserver();
86.50 - String which = req.getParameter("which");
86.51 -
86.52 - if(which != null && which.equals("config"))
86.53 - {
86.54 - whichConfig(req, resp);
86.55 - }
86.56 - else if(which != null && which.equals("groupadd"))
86.57 - {
86.58 - whichGroupAdd(req, resp);
86.59 - }
86.60 - else if(which != null && which.equals("groupdelete"))
86.61 - {
86.62 - whichGroupDelete(req, resp);
86.63 - }
86.64 - else
86.65 - {
86.66 - whichNone(req, resp);
86.67 - }
86.68 -
86.69 - disconnectFromNewsserver();
86.70 - }
86.71 - }
86.72 -
86.73 - private void whichConfig(HttpServletRequest req, HttpServletResponse resp)
86.74 - throws IOException
86.75 - {
86.76 - StringBuilder keys = new StringBuilder();
86.77 -
86.78 - Set pnames = req.getParameterMap().keySet();
86.79 - for(Object obj : pnames)
86.80 - {
86.81 - String pname = (String)obj;
86.82 - if(pname.startsWith("configkey:"))
86.83 - {
86.84 - String value = req.getParameter(pname);
86.85 - String key = pname.split(":")[1];
86.86 - if(!value.equals("<not set>"))
86.87 - {
86.88 - printlnToNewsserver("XDAEMON SET " + key + " " + value);
86.89 - readlnFromNewsserver();
86.90 -
86.91 - keys.append(key);
86.92 - keys.append("<br/>");
86.93 - }
86.94 - }
86.95 - }
86.96 -
86.97 - StringTemplate tmpl = getTemplate("ConfigUpdated.tmpl");
86.98 -
86.99 - tmpl.set("UPDATED_KEYS", keys.toString());
86.100 -
86.101 - resp.setStatus(HttpServletResponse.SC_OK);
86.102 - resp.getWriter().println(tmpl.toString());
86.103 - resp.getWriter().flush();
86.104 - }
86.105 -
86.106 - private void whichGroupAdd(HttpServletRequest req, HttpServletResponse resp)
86.107 - throws IOException
86.108 - {
86.109 - String[] groupnames = req.getParameter("groups").split("\n");
86.110 -
86.111 - for(String groupname : groupnames)
86.112 - {
86.113 - groupname = groupname.trim();
86.114 - if(groupname.equals(""))
86.115 - {
86.116 - continue;
86.117 - }
86.118 -
86.119 - printlnToNewsserver("XDAEMON GROUPADD " + groupname + " 0");
86.120 - String line = readlnFromNewsserver();
86.121 - if(!line.startsWith("200 "))
86.122 - {
86.123 - System.out.println("Warning " + groupname + " probably not created!");
86.124 - }
86.125 - }
86.126 -
86.127 - StringTemplate tmpl = getTemplate("GroupAdded.tmpl");
86.128 -
86.129 - tmpl.set("GROUP", req.getParameter("groups"));
86.130 -
86.131 - resp.setStatus(HttpServletResponse.SC_OK);
86.132 - resp.getWriter().println(tmpl.toString());
86.133 - resp.getWriter().flush();
86.134 - }
86.135 -
86.136 - private void whichGroupDelete(HttpServletRequest req, HttpServletResponse resp)
86.137 - throws IOException
86.138 - {
86.139 - String groupname = req.getParameter("group");
86.140 - printlnToNewsserver("XDAEMON GROUPDEL " + groupname);
86.141 - String line = readlnFromNewsserver();
86.142 - if(!line.startsWith("200 "))
86.143 - throw new IOException(line);
86.144 -
86.145 - StringTemplate tmpl = getTemplate("GroupDeleted.tmpl");
86.146 -
86.147 - tmpl.set("GROUP", groupname);
86.148 -
86.149 - resp.setStatus(HttpServletResponse.SC_OK);
86.150 - resp.getWriter().println(tmpl.toString());
86.151 - resp.getWriter().flush();
86.152 - }
86.153 -
86.154 - private void whichNone(HttpServletRequest req, HttpServletResponse resp)
86.155 - throws IOException
86.156 - {
86.157 - StringTemplate tmpl = getTemplate("SonewsConfigServlet.tmpl");
86.158 -
86.159 - // Retrieve config keys from server
86.160 - List<String> configKeys = new ArrayList<String>();
86.161 - printlnToNewsserver("XDAEMON LIST CONFIGKEYS");
86.162 - String line = readlnFromNewsserver();
86.163 - if(!line.startsWith("200 "))
86.164 - throw new IOException("XDAEMON command not supported!");
86.165 - for(;;)
86.166 - {
86.167 - line = readlnFromNewsserver();
86.168 - if(line.equals("."))
86.169 - break;
86.170 - else
86.171 - configKeys.add(line);
86.172 - }
86.173 -
86.174 - // Construct config table
86.175 - StringBuilder strb = new StringBuilder();
86.176 - for(String key : configKeys)
86.177 - {
86.178 - strb.append("<tr><td><code>");
86.179 - strb.append(key);
86.180 - strb.append("</code></td><td>");
86.181 -
86.182 - // Retrieve config value from server
86.183 - String value = "<not set>";
86.184 - printlnToNewsserver("XDAEMON GET " + key);
86.185 - line = readlnFromNewsserver();
86.186 - if(line.startsWith("200 "))
86.187 - {
86.188 - value = readlnFromNewsserver();
86.189 - readlnFromNewsserver(); // Read the "."
86.190 - }
86.191 -
86.192 - strb.append("<input type=text name=\"configkey:");
86.193 - strb.append(key);
86.194 - strb.append("\" value=\"");
86.195 - strb.append(value);
86.196 - strb.append("\"/></td></tr>");
86.197 - }
86.198 - tmpl.set("CONFIG", strb.toString());
86.199 -
86.200 - // Retrieve served newsgroup names from server
86.201 - List<String> groups = new ArrayList<String>();
86.202 - printlnToNewsserver("LIST");
86.203 - line = readlnFromNewsserver();
86.204 - if(line.startsWith("215 "))
86.205 - {
86.206 - for(;;)
86.207 - {
86.208 - line = readlnFromNewsserver();
86.209 - if(line.equals("."))
86.210 - {
86.211 - break;
86.212 - }
86.213 - else
86.214 - {
86.215 - groups.add(line.split(" ")[0]);
86.216 - }
86.217 - }
86.218 - }
86.219 - else
86.220 - throw new IOException("Error issuing LIST command!");
86.221 -
86.222 - // Construct groups list
86.223 - StringTemplate tmplGroupList = new StringTemplate(
86.224 - Resource.getAsString("org/sonews/web/tmpl/GroupList.tmpl", true));
86.225 - strb = new StringBuilder();
86.226 - for(String group : groups)
86.227 - {
86.228 - tmplGroupList.set("GROUPNAME", group);
86.229 - strb.append(tmplGroupList.toString());
86.230 - }
86.231 - tmpl.set("GROUP", strb.toString());
86.232 -
86.233 - // Set server name
86.234 - tmpl.set("SERVERNAME", hello.split(" ")[2]);
86.235 - tmpl.set("TITLE", "Configuration");
86.236 -
86.237 - resp.getWriter().println(tmpl.toString());
86.238 - resp.getWriter().flush();
86.239 - resp.setStatus(HttpServletResponse.SC_OK);
86.240 - }
86.241 -
86.242 -}
87.1 --- a/org/sonews/web/SonewsGroupServlet.java Wed Jul 01 10:48:22 2009 +0200
87.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
87.3 @@ -1,66 +0,0 @@
87.4 -/*
87.5 - * SONEWS News Server
87.6 - * see AUTHORS for the list of contributors
87.7 - *
87.8 - * This program is free software: you can redistribute it and/or modify
87.9 - * it under the terms of the GNU General Public License as published by
87.10 - * the Free Software Foundation, either version 3 of the License, or
87.11 - * (at your option) any later version.
87.12 - *
87.13 - * This program is distributed in the hope that it will be useful,
87.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
87.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
87.16 - * GNU General Public License for more details.
87.17 - *
87.18 - * You should have received a copy of the GNU General Public License
87.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
87.20 - */
87.21 -
87.22 -package org.sonews.web;
87.23 -
87.24 -import java.io.IOException;
87.25 -import javax.servlet.http.HttpServletRequest;
87.26 -import javax.servlet.http.HttpServletResponse;
87.27 -import org.sonews.util.StringTemplate;
87.28 -
87.29 -/**
87.30 - * Views the group settings and allows editing.
87.31 - * @author Christian Lins
87.32 - * @since sonews/0.5.0
87.33 - */
87.34 -public class SonewsGroupServlet extends AbstractSonewsServlet
87.35 -{
87.36 -
87.37 - @Override
87.38 - public void doGet(HttpServletRequest req, HttpServletResponse resp)
87.39 - throws IOException
87.40 - {
87.41 - synchronized(this)
87.42 - {
87.43 - connectToNewsserver();
87.44 - String name = req.getParameter("name");
87.45 - String action = req.getParameter("action");
87.46 -
87.47 - if("set_flags".equals(action))
87.48 - {
87.49 -
87.50 - }
87.51 - else if("set_mladdress".equals(action))
87.52 - {
87.53 -
87.54 - }
87.55 -
87.56 - StringTemplate tmpl = getTemplate("SonewsGroupServlet.tmpl");
87.57 - tmpl.set("SERVERNAME", hello.split(" ")[2]);
87.58 - tmpl.set("TITLE", "Group " + name);
87.59 - tmpl.set("GROUPNAME", name);
87.60 -
87.61 - resp.getWriter().println(tmpl.toString());
87.62 - resp.getWriter().flush();
87.63 - resp.setStatus(HttpServletResponse.SC_OK);
87.64 -
87.65 - disconnectFromNewsserver();
87.66 - }
87.67 - }
87.68 -
87.69 -}
88.1 --- a/org/sonews/web/SonewsPeerServlet.java Wed Jul 01 10:48:22 2009 +0200
88.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
88.3 @@ -1,95 +0,0 @@
88.4 -/*
88.5 - * SONEWS News Server
88.6 - * see AUTHORS for the list of contributors
88.7 - *
88.8 - * This program is free software: you can redistribute it and/or modify
88.9 - * it under the terms of the GNU General Public License as published by
88.10 - * the Free Software Foundation, either version 3 of the License, or
88.11 - * (at your option) any later version.
88.12 - *
88.13 - * This program is distributed in the hope that it will be useful,
88.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
88.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
88.16 - * GNU General Public License for more details.
88.17 - *
88.18 - * You should have received a copy of the GNU General Public License
88.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
88.20 - */
88.21 -
88.22 -package org.sonews.web;
88.23 -
88.24 -import java.io.IOException;
88.25 -import java.util.HashSet;
88.26 -import javax.servlet.http.HttpServletRequest;
88.27 -import javax.servlet.http.HttpServletResponse;
88.28 -import org.sonews.util.StringTemplate;
88.29 -
88.30 -/**
88.31 - * Servlet that shows the Peers and the Peering Rules.
88.32 - * @author Christian Lins
88.33 - * @since sonews/0.5.0
88.34 - */
88.35 -public class SonewsPeerServlet extends AbstractSonewsServlet
88.36 -{
88.37 -
88.38 - private static final long serialVersionUID = 245345346356L;
88.39 -
88.40 - @Override
88.41 - public void doGet(HttpServletRequest req, HttpServletResponse resp)
88.42 - throws IOException
88.43 - {
88.44 - synchronized(this)
88.45 - {
88.46 - connectToNewsserver();
88.47 - StringTemplate tmpl = getTemplate("SonewsPeerServlet.tmpl");
88.48 -
88.49 - // Read peering rules from newsserver
88.50 - printlnToNewsserver("XDAEMON LIST PEERINGRULES");
88.51 - String line = readlnFromNewsserver();
88.52 - if(!line.startsWith("200 "))
88.53 - {
88.54 - throw new IOException("Unexpected reply: " + line);
88.55 - }
88.56 -
88.57 - // Create FEED_RULES String
88.58 - HashSet<String> peers = new HashSet<String>();
88.59 - StringBuilder feedRulesStr = new StringBuilder();
88.60 - for(;;)
88.61 - {
88.62 - line = readlnFromNewsserver();
88.63 - if(line.equals("."))
88.64 - {
88.65 - break;
88.66 - }
88.67 - else
88.68 - {
88.69 - feedRulesStr.append(line);
88.70 - feedRulesStr.append("<br/>");
88.71 -
88.72 - String[] lineChunks = line.split(" ");
88.73 - peers.add(lineChunks[1]);
88.74 - }
88.75 - }
88.76 -
88.77 - // Create PEERS string
88.78 - StringBuilder peersStr = new StringBuilder();
88.79 - for(String peer : peers)
88.80 - {
88.81 - peersStr.append(peer);
88.82 - peersStr.append("<br/>");
88.83 - }
88.84 -
88.85 - // Set server name
88.86 - tmpl.set("PEERS", peersStr.toString());
88.87 - tmpl.set("PEERING_RULES", feedRulesStr.toString());
88.88 - tmpl.set("SERVERNAME", hello.split(" ")[2]);
88.89 - tmpl.set("TITLE", "Peers");
88.90 -
88.91 - resp.getWriter().println(tmpl.toString());
88.92 - resp.getWriter().flush();
88.93 - resp.setStatus(HttpServletResponse.SC_OK);
88.94 - disconnectFromNewsserver();
88.95 - }
88.96 - }
88.97 -
88.98 -}
89.1 --- a/org/sonews/web/SonewsServlet.java Wed Jul 01 10:48:22 2009 +0200
89.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
89.3 @@ -1,127 +0,0 @@
89.4 -/*
89.5 - * SONEWS News Server
89.6 - * see AUTHORS for the list of contributors
89.7 - *
89.8 - * This program is free software: you can redistribute it and/or modify
89.9 - * it under the terms of the GNU General Public License as published by
89.10 - * the Free Software Foundation, either version 3 of the License, or
89.11 - * (at your option) any later version.
89.12 - *
89.13 - * This program is distributed in the hope that it will be useful,
89.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
89.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
89.16 - * GNU General Public License for more details.
89.17 - *
89.18 - * You should have received a copy of the GNU General Public License
89.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
89.20 - */
89.21 -
89.22 -package org.sonews.web;
89.23 -
89.24 -import java.io.IOException;
89.25 -import javax.servlet.http.HttpServletRequest;
89.26 -import javax.servlet.http.HttpServletResponse;
89.27 -import org.sonews.daemon.Main;
89.28 -import org.sonews.util.StringTemplate;
89.29 -
89.30 -/**
89.31 - * Main sonews webpage servlet.
89.32 - * @author Christian Lins
89.33 - * @since sonews/0.5.0
89.34 - */
89.35 -public class SonewsServlet extends AbstractSonewsServlet
89.36 -{
89.37 -
89.38 - private static final long serialVersionUID = 2392837459834L;
89.39 -
89.40 - @Override
89.41 - public void doGet(HttpServletRequest res, HttpServletResponse resp)
89.42 - throws IOException
89.43 - {
89.44 - synchronized(this)
89.45 - {
89.46 - connectToNewsserver();
89.47 -
89.48 - String line;
89.49 - int connectedClients = 0;
89.50 - int hostedGroups = 0;
89.51 - int hostedNews = 0;
89.52 -
89.53 - printlnToNewsserver("XDAEMON LOG CONNECTED_CLIENTS");
89.54 -
89.55 - line = readlnFromNewsserver();
89.56 - if(!line.startsWith("200 "))
89.57 - {
89.58 - throw new IOException("XDAEMON command not allowed by server");
89.59 - }
89.60 - line = readlnFromNewsserver();
89.61 - connectedClients = Integer.parseInt(line);
89.62 - line = readlnFromNewsserver(); // Read the "."
89.63 -
89.64 - printlnToNewsserver("XDAEMON LOG HOSTED_NEWS");
89.65 - line = readlnFromNewsserver();
89.66 - if(!line.startsWith("200 "))
89.67 - {
89.68 - throw new IOException("XDAEMON command not allowed by server");
89.69 - }
89.70 - line = readlnFromNewsserver();
89.71 - hostedNews = Integer.parseInt(line);
89.72 - line = readlnFromNewsserver(); // read the "."
89.73 -
89.74 - printlnToNewsserver("XDAEMON LOG HOSTED_GROUPS");
89.75 - line = readlnFromNewsserver();
89.76 - if(!line.startsWith("200 "))
89.77 - {
89.78 - throw new IOException("XDAEMON command not allowed by server");
89.79 - }
89.80 - line = readlnFromNewsserver();
89.81 - hostedGroups = Integer.parseInt(line);
89.82 - line = readlnFromNewsserver(); // read the "."
89.83 -
89.84 - printlnToNewsserver("XDAEMON LOG POSTED_NEWS_PER_HOUR");
89.85 - line = readlnFromNewsserver();
89.86 - if(!line.startsWith("200 "))
89.87 - {
89.88 - throw new IOException("XDAEMON command not allowed by server");
89.89 - }
89.90 - String postedNewsPerHour = readlnFromNewsserver();
89.91 - readlnFromNewsserver();
89.92 -
89.93 - printlnToNewsserver("XDAEMON LOG GATEWAYED_NEWS_PER_HOUR");
89.94 - line = readlnFromNewsserver();
89.95 - if(!line.startsWith("200 "))
89.96 - {
89.97 - throw new IOException("XDAEMON command not allowed by server");
89.98 - }
89.99 - String gatewayedNewsPerHour = readlnFromNewsserver();
89.100 - line = readlnFromNewsserver();
89.101 -
89.102 - printlnToNewsserver("XDAEMON LOG FEEDED_NEWS_PER_HOUR");
89.103 - line = readlnFromNewsserver();
89.104 - if(!line.startsWith("200 "))
89.105 - {
89.106 - throw new IOException("XDAEMON command not allowed by server");
89.107 - }
89.108 - String feededNewsPerHour = readlnFromNewsserver();
89.109 - line = readlnFromNewsserver();
89.110 -
89.111 - StringTemplate tmpl = getTemplate("SonewsServlet.tmpl");
89.112 - tmpl.set("SERVERNAME", hello.split(" ")[2]);
89.113 - tmpl.set("STARTDATE", Main.STARTDATE);
89.114 - tmpl.set("ACTIVE_CONNECTIONS", connectedClients);
89.115 - tmpl.set("STORED_NEWS", hostedNews);
89.116 - tmpl.set("SERVED_NEWSGROUPS", hostedGroups);
89.117 - tmpl.set("POSTED_NEWS", postedNewsPerHour);
89.118 - tmpl.set("GATEWAYED_NEWS", gatewayedNewsPerHour);
89.119 - tmpl.set("FEEDED_NEWS", feededNewsPerHour);
89.120 - tmpl.set("TITLE", "Overview");
89.121 -
89.122 - resp.getWriter().println(tmpl.toString());
89.123 - resp.getWriter().flush();
89.124 - resp.setStatus(HttpServletResponse.SC_OK);
89.125 -
89.126 - disconnectFromNewsserver();
89.127 - }
89.128 - }
89.129 -
89.130 -}
90.1 --- a/org/sonews/web/package.html Wed Jul 01 10:48:22 2009 +0200
90.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
90.3 @@ -1,3 +0,0 @@
90.4 -Contains classes of the sonews web interface. These classes are not needed by
90.5 -the running sonews daemon but by the Servlet container
90.6 -<a href="http://kitten.sonews.org/">Kitten</a>.
90.7 \ No newline at end of file
91.1 --- a/org/sonews/web/tmpl/AbstractSonewsServlet.tmpl Wed Jul 01 10:48:22 2009 +0200
91.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
91.3 @@ -1,14 +0,0 @@
91.4 -<html>
91.5 -<head>
91.6 - <title>%SERVERNAME - %TITLE</title>
91.7 - <link rel="stylesheet" type="text/css" href="%STYLESHEET" />
91.8 -</head>
91.9 -
91.10 -<body>
91.11 -<div class="pagetitle">sonews - %TITLE</div>
91.12 -
91.13 -<div class="content">
91.14 -%CONTENT
91.15 -</div>
91.16 -</body>
91.17 -</html>
91.18 \ No newline at end of file
92.1 --- a/org/sonews/web/tmpl/ConfigUpdated.tmpl Wed Jul 01 10:48:22 2009 +0200
92.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
92.3 @@ -1,7 +0,0 @@
92.4 - <p>
92.5 - The following config keys were updated: <br/>
92.6 - %UPDATED_KEYS
92.7 - </p>
92.8 - <p>
92.9 - <a href="/sonews/config">Back to Config</a>
92.10 - </p>
92.11 \ No newline at end of file
93.1 --- a/org/sonews/web/tmpl/GroupAdded.tmpl Wed Jul 01 10:48:22 2009 +0200
93.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
93.3 @@ -1,6 +0,0 @@
93.4 - <p>
93.5 - The Newsgroup %GROUP has been created!
93.6 - </p>
93.7 - <p>
93.8 - <a href="/sonews/config">Back to Config</a>
93.9 - </p>
93.10 \ No newline at end of file
94.1 --- a/org/sonews/web/tmpl/GroupDeleted.tmpl Wed Jul 01 10:48:22 2009 +0200
94.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
94.3 @@ -1,6 +0,0 @@
94.4 - <p>
94.5 - The Newsgroup %GROUP and all associated articles have been deleted!
94.6 - </p>
94.7 - <p>
94.8 - <a href="/sonews/config">Back to Config</a>
94.9 - </p>
94.10 \ No newline at end of file
95.1 --- a/org/sonews/web/tmpl/GroupList.tmpl Wed Jul 01 10:48:22 2009 +0200
95.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
95.3 @@ -1,8 +0,0 @@
95.4 -<tr>
95.5 - <td>
95.6 - <a href="/sonews/group?name=%GROUPNAME">%GROUPNAME</a>
95.7 - </td>
95.8 - <td>
95.9 - <a href="?which=groupdelete&group=%GROUPNAME">delete</a>
95.10 - </td>
95.11 -</tr>
95.12 \ No newline at end of file
96.1 --- a/org/sonews/web/tmpl/SonewsConfigServlet.tmpl Wed Jul 01 10:48:22 2009 +0200
96.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
96.3 @@ -1,30 +0,0 @@
96.4 -<p>
96.5 -<a href="/sonews">Back to Main Page</a>
96.6 -</p>
96.7 -
96.8 -<h2>Configuration values</h2>
96.9 -<form action="/sonews/config" method="GET">
96.10 -<input type="hidden" name="which" value="config"/>
96.11 -<table>
96.12 -%CONFIG
96.13 -</table>
96.14 -<input type="submit" value="Apply changes"/>
96.15 -</form>
96.16 -
96.17 -<h2>Groups served by this sonews instance</h2>
96.18 -<table>
96.19 -%GROUP
96.20 -</table>
96.21 -
96.22 -<p>
96.23 -<h2>Add new group to be served</h2>
96.24 -<form action="/sonews/config" method="GET">
96.25 - <input type="hidden" name="which" value="groupadd"/>
96.26 -<table>
96.27 - <tr><td>Names (separated by newlines):</td>
96.28 - <td><textarea cols="50" rows="15" name="groups"></textarea></td></tr>
96.29 - </tr>
96.30 -</table>
96.31 -<input type="submit" value="Add groups"/>
96.32 -</form>
96.33 -</p>
96.34 \ No newline at end of file
97.1 --- a/org/sonews/web/tmpl/SonewsGroupServlet.tmpl Wed Jul 01 10:48:22 2009 +0200
97.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
97.3 @@ -1,36 +0,0 @@
97.4 -<p>
97.5 -<a href="/sonews">Back to Main Page</a>
97.6 -</p>
97.7 -
97.8 -<h1>Group %GROUPNAME</h1>
97.9 -
97.10 -<h2>Configuration</h2>
97.11 -<h3>General</h3>
97.12 -<form action="" method="GET">
97.13 - <input type="hidden" name="name" value="%GROUPNAME" />
97.14 - <input type="hidden" name="action" value="set_flags"/>
97.15 - Is mirrored Mailinglist?:
97.16 - <input type="checkbox" name="flag0" value="true" />
97.17 - <br/>
97.18 - <input type="submit" value="Update" />
97.19 -</form>
97.20 -
97.21 -<h3>Mailinglist</h3>
97.22 -<form action="" method="GET">
97.23 - <input type="hidden" name="name" value="%GROUPNAME" />
97.24 - <input type="hidden" name="action" value="set_mladdress"/>
97.25 - Mailinglist address:
97.26 - <input type="text" name="mladdress" />
97.27 - <br/>
97.28 - <input type="submit" value="Update" />
97.29 -</form>
97.30 -
97.31 -<h2>Statistics</h2>
97.32 -<h3>Posted mails yesterday</h3>
97.33 -<img src="/sonews/chart?name=postednewsyesterday&group=%GROUPNAME" />
97.34 -
97.35 -<h3>Gatewayed mails yesterday</h3>
97.36 -<img src="/sonews/chart?name=gatewayednewsyesterday&group=%GROUPNAME" />
97.37 -
97.38 -<h3>Feeded news</h3>
97.39 -<img src="/sonews/chart?name=feedednewsyesterday&group=%GROUPNAME" />
97.40 \ No newline at end of file
98.1 --- a/org/sonews/web/tmpl/SonewsPeerServlet.tmpl Wed Jul 01 10:48:22 2009 +0200
98.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
98.3 @@ -1,52 +0,0 @@
98.4 -<p>
98.5 -<a href="/sonews">Back to Main Page</a>
98.6 -</p>
98.7 -
98.8 -On this page you can configure to which peer hosts new messages are
98.9 -posted or from which peer hosts we should receive messages.
98.10 -
98.11 -<h2>Peers</h2>
98.12 -%PEERS
98.13 -
98.14 -<h3>Add new peer</h3>
98.15 -<form action="" method="GET">
98.16 -<table style="border: 0px">
98.17 - <tr>
98.18 - <td>Hostname:</td>
98.19 - <td><input type="text" name="host" value="news.someremotehost.org" /></td>
98.20 - </tr>
98.21 - <tr>
98.22 - <td>Port:</td>
98.23 - <td><input type="text" name="port" value="119" /></td>
98.24 - </tr>
98.25 -</table>
98.26 -<input type="submit" value="Add peer" />
98.27 -</form>
98.28 -
98.29 -<h2>Rules</h2>
98.30 -<h3>Current</h3>
98.31 -%PEERING_RULES
98.32 -
98.33 -<h3>Add peering rule</h3>
98.34 -<form action="" method="GET">
98.35 -<table style="border: 0px">
98.36 - <tr>
98.37 - <td>Peer:</td>
98.38 - <td>%OPTION_PEERS</td>
98.39 - </tr>
98.40 - <tr>
98.41 - <td>Group:</td>
98.42 - <td>%OPTION_GROUP</td>
98.43 - </tr>
98.44 - <tr>
98.45 - <td>Peering type:</td>
98.46 - <td>
98.47 - <select name="peertype">
98.48 - <option value="0">Pull peering</option>
98.49 - <option value="1">Push peering</option>
98.50 - </select>
98.51 - </td>
98.52 - </tr>
98.53 -</table>
98.54 -<input type="submit" value="Add peering rule" />
98.55 -</form>
98.56 \ No newline at end of file
99.1 --- a/org/sonews/web/tmpl/SonewsServlet.tmpl Wed Jul 01 10:48:22 2009 +0200
99.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
99.3 @@ -1,54 +0,0 @@
99.4 -This server is running since %STARTDATE.
99.5 -
99.6 -<h2>Configuration</h2>
99.7 -Here you can edit most of the configuration values for %SERVERNAME.
99.8 -Please note that some of these values require a server restart,
99.9 -some do not and are valid immediately.
99.10 -<ul>
99.11 - <li><a href="/sonews/config">View/Edit configuration values</a></li>
99.12 - <li><a href="/sonews/peer">View/Edit peer settings</a></li>
99.13 -</ul>
99.14 -
99.15 -<h2>Statistics & Logs</h2>
99.16 -Here is a short overview of useful statistics of %SERVERNAME. Click on a
99.17 -stat key to get more details.
99.18 -<table style="border: 1px dotted black">
99.19 - <tr>
99.20 - <td><i>Stat key</i></td>
99.21 - <td><i>Value</i></td>
99.22 - </tr>
99.23 -
99.24 - <tr>
99.25 - <td>Active connections:</td>
99.26 - <td>%ACTIVE_CONNECTIONS</td>
99.27 - </tr>
99.28 -
99.29 - <tr>
99.30 - <td>Served newsgroups:</td>
99.31 - <td>%SERVED_NEWSGROUPS</td>
99.32 - </tr>
99.33 -
99.34 - <tr>
99.35 - <td>Stored news messages:</td>
99.36 - <td>%STORED_NEWS</td>
99.37 - </tr>
99.38 -
99.39 - <tr>
99.40 - <td><a href="/sonews/chart?name=postednewsyesterday">Posted news</a>:</td>
99.41 - <td>%POSTED_NEWS per hour</td>
99.42 - </tr>
99.43 -
99.44 - <tr>
99.45 - <td><a href="/sonews/chart?name=gatewayednewsyesterday">Gatewayed news</a>:</td>
99.46 - <td>%GATEWAYED_NEWS per hour</td>
99.47 - </tr>
99.48 -
99.49 - <tr>
99.50 - <td><a href="/sonews/chart?name=feedednewsyesterday">Feeded news</a>:</td>
99.51 - <td>%FEEDED_NEWS per hour</td>
99.52 - </tr>
99.53 -</table>
99.54 -
99.55 -<h2>Documentation</h2>
99.56 -You'll find the most recent
99.57 -<a href="http://www.sonews.org/">documentation of %SERVERNAME here</a>.
99.58 \ No newline at end of file