sonews/1.0.0
authorchris <chris@marvin>
Wed, 22 Jul 2009 14:04:05 +0200
changeset 32fdc9cc89502
parent 2 1090e2141798
child 4 cdddff328b78
child 8 b62fe6ed39d3
sonews/1.0.0
.hgtags
DEBIAN-web/README.Debian
DEBIAN-web/compat
DEBIAN-web/control
DEBIAN-web/docs
DEBIAN/control
bin/sonews.sh
doc/sonews.xml
helpers/commands.list
helpers/database_postgresql8_tmpl.sql
helpers/sonews
helpers/usage
makedeb
org/sonews/Main.java
org/sonews/ShutdownHook.java
org/sonews/config/AbstractConfig.java
org/sonews/config/BackendConfig.java
org/sonews/config/CommandLineConfig.java
org/sonews/config/Config.java
org/sonews/config/FileConfig.java
org/sonews/daemon/AbstractDaemon.java
org/sonews/daemon/BootstrapConfig.java
org/sonews/daemon/ChannelLineBuffers.java
org/sonews/daemon/ChannelReader.java
org/sonews/daemon/CommandSelector.java
org/sonews/daemon/Config.java
org/sonews/daemon/Connections.java
org/sonews/daemon/Main.java
org/sonews/daemon/NNTPConnection.java
org/sonews/daemon/NNTPDaemon.java
org/sonews/daemon/ShutdownHook.java
org/sonews/daemon/command/AbstractCommand.java
org/sonews/daemon/command/ArticleCommand.java
org/sonews/daemon/command/CapabilitiesCommand.java
org/sonews/daemon/command/Command.java
org/sonews/daemon/command/GroupCommand.java
org/sonews/daemon/command/HelpCommand.java
org/sonews/daemon/command/ListCommand.java
org/sonews/daemon/command/ListGroupCommand.java
org/sonews/daemon/command/ModeReaderCommand.java
org/sonews/daemon/command/NewGroupsCommand.java
org/sonews/daemon/command/NextPrevCommand.java
org/sonews/daemon/command/OverCommand.java
org/sonews/daemon/command/PostCommand.java
org/sonews/daemon/command/QuitCommand.java
org/sonews/daemon/command/StatCommand.java
org/sonews/daemon/command/UnsupportedCommand.java
org/sonews/daemon/command/XDaemonCommand.java
org/sonews/daemon/command/XPatCommand.java
org/sonews/daemon/storage/Article.java
org/sonews/daemon/storage/ArticleHead.java
org/sonews/daemon/storage/Database.java
org/sonews/daemon/storage/Group.java
org/sonews/daemon/storage/Headers.java
org/sonews/daemon/storage/package.html
org/sonews/feed/FeedManager.java
org/sonews/feed/PullFeeder.java
org/sonews/feed/PushFeeder.java
org/sonews/mlgw/Dispatcher.java
org/sonews/mlgw/MailPoller.java
org/sonews/mlgw/SMTPTransport.java
org/sonews/storage/AggregatedGroup.java
org/sonews/storage/Article.java
org/sonews/storage/ArticleHead.java
org/sonews/storage/Channel.java
org/sonews/storage/Group.java
org/sonews/storage/Headers.java
org/sonews/storage/Storage.java
org/sonews/storage/StorageBackendException.java
org/sonews/storage/StorageManager.java
org/sonews/storage/StorageProvider.java
org/sonews/storage/impl/JDBCDatabase.java
org/sonews/storage/impl/JDBCDatabaseProvider.java
org/sonews/storage/package.html
org/sonews/util/AbstractConfig.java
org/sonews/util/DatabaseSetup.java
org/sonews/util/Log.java
org/sonews/util/Purger.java
org/sonews/util/Stats.java
org/sonews/util/io/ArticleInputStream.java
org/sonews/util/io/ArticleReader.java
org/sonews/util/io/ArticleWriter.java
org/sonews/util/io/VarCharsetReader.java
org/sonews/web/AbstractSonewsServlet.java
org/sonews/web/MemoryBitmapChart.java
org/sonews/web/SonewsChartServlet.java
org/sonews/web/SonewsConfigServlet.java
org/sonews/web/SonewsGroupServlet.java
org/sonews/web/SonewsPeerServlet.java
org/sonews/web/SonewsServlet.java
org/sonews/web/package.html
org/sonews/web/tmpl/AbstractSonewsServlet.tmpl
org/sonews/web/tmpl/ConfigUpdated.tmpl
org/sonews/web/tmpl/GroupAdded.tmpl
org/sonews/web/tmpl/GroupDeleted.tmpl
org/sonews/web/tmpl/GroupList.tmpl
org/sonews/web/tmpl/SonewsConfigServlet.tmpl
org/sonews/web/tmpl/SonewsGroupServlet.tmpl
org/sonews/web/tmpl/SonewsPeerServlet.tmpl
org/sonews/web/tmpl/SonewsServlet.tmpl
     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>&lsquo;<literal>sonews.hostname</literal>&rsquo;</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>&lsquo;<literal>sonews.timeout</literal>&rsquo;</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>&lsquo;<literal>sonews.port</literal>&rsquo;</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>&lsquo;<literal>sonews.xdaemon.host</literal>&rsquo;</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