Drupal: zprávy jsou multipart/alternative – jak prostý text, tak XHTML. TODO: filtry prostého textu a XHTML
authorFrantišek Kučera <franta-hg@frantovo.cz>
Wed Oct 12 00:11:25 2011 +0200 (2011-10-12)
changeset 72aae4b4688700
parent 71 beb11d70f0eb
child 73 1feed5fbf147
Drupal: zprávy jsou multipart/alternative – jak prostý text, tak XHTML. TODO: filtry prostého textu a XHTML
zatím se do všech zpráv vkládá stejný vycpávkový text.
src/org/sonews/storage/DrupalArticle.java
src/org/sonews/storage/DrupalMessage.java
src/org/sonews/storage/impl/DrupalDatabase.java
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/src/org/sonews/storage/DrupalArticle.java	Wed Oct 12 00:11:25 2011 +0200
     1.3 @@ -0,0 +1,46 @@
     1.4 +/*
     1.5 + *   SONEWS News Server
     1.6 + *   see AUTHORS for the list of contributors
     1.7 + *
     1.8 + *   This program is free software: you can redistribute it and/or modify
     1.9 + *   it under the terms of the GNU General Public License as published by
    1.10 + *   the Free Software Foundation, either version 3 of the License, or
    1.11 + *   (at your option) any later version.
    1.12 + *
    1.13 + *   This program is distributed in the hope that it will be useful,
    1.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
    1.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    1.16 + *   GNU General Public License for more details.
    1.17 + *
    1.18 + *   You should have received a copy of the GNU General Public License
    1.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
    1.20 + */
    1.21 +package org.sonews.storage;
    1.22 +
    1.23 +import java.io.IOException;
    1.24 +import java.util.Enumeration;
    1.25 +import javax.mail.Header;
    1.26 +import javax.mail.MessagingException;
    1.27 +import javax.mail.internet.InternetHeaders;
    1.28 +
    1.29 +/**
    1.30 + *
    1.31 + * @author František Kučera (frantovo.cz)
    1.32 + */
    1.33 +public class DrupalArticle extends Article {
    1.34 +
    1.35 +	public DrupalArticle(DrupalMessage msg) throws MessagingException, IOException {
    1.36 +		headers = new InternetHeaders();
    1.37 +
    1.38 +		/** In order to have all headers (like MIME type) */
    1.39 +		msg.saveChanges();
    1.40 +
    1.41 +		for (Enumeration e = msg.getAllHeaders(); e.hasMoreElements();) {
    1.42 +			final Header header = (Header) e.nextElement();
    1.43 +			this.headers.addHeader(header.getName(), header.getValue());
    1.44 +		}
    1.45 +
    1.46 +		setBody(msg.getBody());
    1.47 +		validateHeaders();
    1.48 +	}
    1.49 +}
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/src/org/sonews/storage/DrupalMessage.java	Wed Oct 12 00:11:25 2011 +0200
     2.3 @@ -0,0 +1,193 @@
     2.4 +/*
     2.5 + *   SONEWS News Server
     2.6 + *   see AUTHORS for the list of contributors
     2.7 + *
     2.8 + *   This program is free software: you can redistribute it and/or modify
     2.9 + *   it under the terms of the GNU General Public License as published by
    2.10 + *   the Free Software Foundation, either version 3 of the License, or
    2.11 + *   (at your option) any later version.
    2.12 + *
    2.13 + *   This program is distributed in the hope that it will be useful,
    2.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
    2.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    2.16 + *   GNU General Public License for more details.
    2.17 + *
    2.18 + *   You should have received a copy of the GNU General Public License
    2.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
    2.20 + */
    2.21 +package org.sonews.storage;
    2.22 +
    2.23 +import java.io.ByteArrayOutputStream;
    2.24 +import java.io.IOException;
    2.25 +import java.io.UnsupportedEncodingException;
    2.26 +import java.sql.ResultSet;
    2.27 +import java.sql.SQLException;
    2.28 +import java.util.ArrayList;
    2.29 +import java.util.Date;
    2.30 +import java.util.Enumeration;
    2.31 +import javax.mail.Header;
    2.32 +import javax.mail.MessagingException;
    2.33 +import javax.mail.Multipart;
    2.34 +import javax.mail.Session;
    2.35 +import javax.mail.internet.InternetAddress;
    2.36 +import javax.mail.internet.MimeBodyPart;
    2.37 +import javax.mail.internet.MimeMessage;
    2.38 +import javax.mail.internet.MimeMultipart;
    2.39 +
    2.40 +/**
    2.41 + * This is MimeMessage which enables custom Message-ID header
    2.42 + * (this header will not be overwritten by the default one like in MimeMessage).
    2.43 + * 
    2.44 + * Also add header and body separate serialization.
    2.45 + * 
    2.46 + * And can be deserialized from SQL ResultSet
    2.47 + * 
    2.48 + * @author František Kučera (frantovo.cz)
    2.49 + */
    2.50 +public class DrupalMessage extends MimeMessage {
    2.51 +
    2.52 +	private static final String MESSAGE_ID_HEADER = "Message-ID";
    2.53 +	private static final String CRLF = "\r\n";
    2.54 +	public static final String CHARSET = "UTF-8";
    2.55 +	private static final String XHTML_CONTENT_TYPE = "text/html; charset=" + CHARSET;
    2.56 +	private String messageID;
    2.57 +
    2.58 +	/**
    2.59 +	 * Constructs MIME message from SQL result.
    2.60 +	 * @param rs ResultSet containing message data. No {@link ResultSet#next()} will be called, just values from current row will be read.
    2.61 +	 * @param constructBody true if whole message should be constructed | false if we need only message headers (body will be dummy).
    2.62 +	 */
    2.63 +	public DrupalMessage(ResultSet rs, String myDomain, boolean constructBody) throws SQLException, UnsupportedEncodingException, MessagingException {
    2.64 +		super(Session.getDefaultInstance(System.getProperties()));
    2.65 +
    2.66 +		addHeader("Message-id", constructMessageId(rs.getInt("id"), rs.getInt("group_id"), rs.getString("group_name"), myDomain));
    2.67 +		addHeader("Newsgroups", rs.getString("group_name"));
    2.68 +		setFrom(new InternetAddress("anonym@example.com", rs.getString("sender_name")));
    2.69 +		setSubject(rs.getString("subject"));
    2.70 +		setSentDate(new Date(rs.getLong("created")));
    2.71 +		
    2.72 +		Integer parentID = rs.getInt("parent_id");
    2.73 +		if (parentID != null && parentID > 0) {
    2.74 +			String parentMessageID = constructMessageId(parentID, rs.getInt("group_id"), rs.getString("group_name"), myDomain);
    2.75 +			addHeader("In-Reply-To", parentMessageID);
    2.76 +			addHeader("References", parentMessageID);
    2.77 +		}
    2.78 +
    2.79 +		if (constructBody) {
    2.80 +			Multipart multipart = new MimeMultipart("alternative");
    2.81 +			setContent(multipart);
    2.82 +
    2.83 +			/** TODO: Plain text part */
    2.84 +			MimeBodyPart textPart = new MimeBodyPart();
    2.85 +			multipart.addBodyPart(textPart);
    2.86 +			textPart.setText(readPlainText(rs));
    2.87 +
    2.88 +			/** TODO: XHTML part */
    2.89 +			MimeBodyPart htmlPart = new MimeBodyPart();
    2.90 +			multipart.addBodyPart(htmlPart);
    2.91 +			htmlPart.setContent(readXhtmlText(rs), XHTML_CONTENT_TYPE);
    2.92 +		} else {
    2.93 +			setText("");
    2.94 +		}
    2.95 +	}
    2.96 +
    2.97 +	private String readPlainText(ResultSet rs) {
    2.98 +		/**
    2.99 +		 * TODO: převést na prostý text
   2.100 +		 */
   2.101 +		return "TODO: obyčejný text";
   2.102 +	}
   2.103 +
   2.104 +	private String readXhtmlText(ResultSet rs) {
   2.105 +		/**
   2.106 +		 * TODO: převést na XHTML
   2.107 +		 */
   2.108 +		return "<html><body>TODO: tady bude nějaký <strong>(X)HTML</strong></body></html>";
   2.109 +	}
   2.110 +
   2.111 +	private static String constructMessageId(int articleID, int groupID, String groupName, String domainName) {
   2.112 +		StringBuilder sb = new StringBuilder();
   2.113 +		sb.append("<");
   2.114 +		sb.append(articleID);
   2.115 +		sb.append("-");
   2.116 +		sb.append(groupID);
   2.117 +		sb.append("-");
   2.118 +		sb.append(groupName);
   2.119 +		sb.append("@");
   2.120 +		sb.append(domainName);
   2.121 +		sb.append(">");
   2.122 +		return sb.toString();
   2.123 +	}
   2.124 +
   2.125 +	@Override
   2.126 +	public void setHeader(String name, String value) throws MessagingException {
   2.127 +		super.setHeader(name, value);
   2.128 +
   2.129 +		if (MESSAGE_ID_HEADER.equalsIgnoreCase(name)) {
   2.130 +			messageID = value;
   2.131 +		}
   2.132 +	}
   2.133 +
   2.134 +	@Override
   2.135 +	public final void addHeader(String name, String value) throws MessagingException {
   2.136 +		super.addHeader(name, value);
   2.137 +
   2.138 +		if (MESSAGE_ID_HEADER.equalsIgnoreCase(name)) {
   2.139 +			messageID = value;
   2.140 +		}
   2.141 +	}
   2.142 +
   2.143 +	@Override
   2.144 +	public void removeHeader(String name) throws MessagingException {
   2.145 +		super.removeHeader(name);
   2.146 +
   2.147 +		if (MESSAGE_ID_HEADER.equalsIgnoreCase(name)) {
   2.148 +			messageID = null;
   2.149 +		}
   2.150 +	}
   2.151 +
   2.152 +	public void setMessageID(String messageID) {
   2.153 +		this.messageID = messageID;
   2.154 +	}
   2.155 +
   2.156 +	@Override
   2.157 +	protected void updateMessageID() throws MessagingException {
   2.158 +		if (messageID == null) {
   2.159 +			super.updateMessageID();
   2.160 +		} else {
   2.161 +			setHeader(MESSAGE_ID_HEADER, messageID);
   2.162 +		}
   2.163 +	}
   2.164 +
   2.165 +	/**
   2.166 +	 * Call {@link #saveChanges()} before this method, if you want all headers including such ones like:
   2.167 +	 * 
   2.168 +	 * <pre>MIME-Version: 1.0
   2.169 +	 *Content-Type: multipart/alternative;</pre>
   2.170 +	 * 
   2.171 +	 * @return serialized headers
   2.172 +	 * @throws MessagingException if getAllHeaders() fails
   2.173 +	 */
   2.174 +	public String getHeaders() throws MessagingException {
   2.175 +		StringBuilder sb = new StringBuilder();
   2.176 +		for (Enumeration eh = getAllHeaderLines(); eh.hasMoreElements();) {
   2.177 +			sb.append(eh.nextElement());
   2.178 +			sb.append(CRLF);
   2.179 +		}
   2.180 +		return sb.toString();
   2.181 +	}
   2.182 +
   2.183 +	public byte[] getBody() throws IOException, MessagingException {
   2.184 +		saveChanges();
   2.185 +
   2.186 +		ArrayList<String> skipHeaders = new ArrayList<String>();
   2.187 +		for (Enumeration eh = getAllHeaders(); eh.hasMoreElements();) {
   2.188 +			Header h = (Header) eh.nextElement();
   2.189 +			skipHeaders.add(h.getName());
   2.190 +		}
   2.191 +
   2.192 +		ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
   2.193 +		writeTo(baos, skipHeaders.toArray(new String[skipHeaders.size()]));
   2.194 +		return baos.toByteArray();
   2.195 +	}
   2.196 +}
     3.1 --- a/src/org/sonews/storage/impl/DrupalDatabase.java	Tue Oct 11 16:34:17 2011 +0200
     3.2 +++ b/src/org/sonews/storage/impl/DrupalDatabase.java	Wed Oct 12 00:11:25 2011 +0200
     3.3 @@ -17,29 +17,26 @@
     3.4   */
     3.5  package org.sonews.storage.impl;
     3.6  
     3.7 -import java.io.UnsupportedEncodingException;
     3.8  import java.sql.Connection;
     3.9  import java.sql.DriverManager;
    3.10  import java.sql.PreparedStatement;
    3.11  import java.sql.ResultSet;
    3.12 -import java.sql.SQLException;
    3.13  import java.sql.Statement;
    3.14  import java.text.SimpleDateFormat;
    3.15  import java.util.ArrayList;
    3.16  import java.util.Collections;
    3.17 -import java.util.Date;
    3.18  import java.util.List;
    3.19  import java.util.Locale;
    3.20  import java.util.logging.Level;
    3.21  import java.util.logging.Logger;
    3.22 -import javax.mail.internet.MailDateFormat;
    3.23 -import javax.mail.internet.MimeUtility;
    3.24 -import org.apache.commons.codec.net.BCodec;
    3.25 +import javax.mail.Message;
    3.26  import org.apache.commons.codec.net.QuotedPrintableCodec;
    3.27  import org.sonews.config.Config;
    3.28  import org.sonews.feed.Subscription;
    3.29  import org.sonews.storage.Article;
    3.30  import org.sonews.storage.ArticleHead;
    3.31 +import org.sonews.storage.DrupalArticle;
    3.32 +import org.sonews.storage.DrupalMessage;
    3.33  import org.sonews.storage.Group;
    3.34  import org.sonews.storage.Storage;
    3.35  import org.sonews.storage.StorageBackendException;
    3.36 @@ -54,14 +51,11 @@
    3.37  	private static final Logger log = Logger.getLogger(DrupalDatabase.class.getName());
    3.38  	public static final String CHARSET = "UTF-8";
    3.39  	public static final String CRLF = "\r\n";
    3.40 -	public static final int MAX_RESTARTS = 2;
    3.41 -	/** How many times the database connection was reinitialized */
    3.42 -	protected int restarts = 0;
    3.43  	protected Connection conn = null;
    3.44  	private QuotedPrintableCodec qpc = new QuotedPrintableCodec(CHARSET);
    3.45  	private SimpleDateFormat RFC822_DATE = new SimpleDateFormat("EEE', 'dd' 'MMM' 'yyyy' 'HH:mm:ss' 'Z", Locale.US);
    3.46  	// TODO: správná doména
    3.47 -	private String myDomain = "kinderporno.cz";
    3.48 +	private String myDomain = "nntp.i1984.cz";
    3.49  
    3.50  	public DrupalDatabase() throws StorageBackendException {
    3.51  		connectDatabase();
    3.52 @@ -137,73 +131,12 @@
    3.53  			return null;
    3.54  		} else {
    3.55  			return Long.parseLong(localPart[1]);
    3.56 +			// If needed:
    3.57 +			// parseGroupName() will be same as this method, just with:
    3.58 +			// return localPart[2];
    3.59  		}
    3.60  	}
    3.61  
    3.62 -	private static String parseGroupName(String messageID) {
    3.63 -		String[] localPart = parseMessageID(messageID);
    3.64 -		if (localPart == null) {
    3.65 -			return null;
    3.66 -		} else {
    3.67 -			return localPart[2];
    3.68 -		}
    3.69 -	}
    3.70 -
    3.71 -	private static String constructMessageId(int articleID, int groupID, String groupName, String domainName) {
    3.72 -		StringBuilder sb = new StringBuilder();
    3.73 -		sb.append("<");
    3.74 -		sb.append(articleID);
    3.75 -		sb.append("-");
    3.76 -		sb.append(groupID);
    3.77 -		sb.append("-");
    3.78 -		sb.append(groupName);
    3.79 -		sb.append("@");
    3.80 -		sb.append(domainName);
    3.81 -		sb.append(">");
    3.82 -		return sb.toString();
    3.83 -	}
    3.84 -
    3.85 -	/**
    3.86 -	 * 
    3.87 -	 * @param sb header list to be appended with new header. List must be terminated by line end.
    3.88 -	 * @param key header name (without : and space)
    3.89 -	 * @param value header value
    3.90 -	 * @param encode true if value should be encoded/escaped before appending
    3.91 -	 * @throws UnsupportedEncodingException 
    3.92 -	 */
    3.93 -	private static void addHeader(StringBuilder sb, String key, String value, boolean encode) throws UnsupportedEncodingException {
    3.94 -		sb.append(key);
    3.95 -		sb.append(": ");
    3.96 -		if (encode) {
    3.97 -			sb.append(MimeUtility.encodeWord(value));
    3.98 -		} else {
    3.99 -			sb.append(value);
   3.100 -		}
   3.101 -		sb.append(CRLF);
   3.102 -	}
   3.103 -
   3.104 -	private String constructHeaders(ResultSet rs) throws SQLException, UnsupportedEncodingException {
   3.105 -		StringBuilder sb = new StringBuilder();
   3.106 -
   3.107 -		addHeader(sb, "Message-id", constructMessageId(rs.getInt("id"), rs.getInt("group_id"), rs.getString("group_name"), myDomain), false);
   3.108 -		addHeader(sb, "From", MimeUtility.encodeWord(rs.getString("sender_name")) + " <>", false);
   3.109 -		addHeader(sb, "Subject", rs.getString("subject"), true);
   3.110 -		/** TODO: správný formát data: */
   3.111 -		addHeader(sb, "Date", RFC822_DATE.format(new Date(rs.getLong("created"))), false);
   3.112 -		addHeader(sb, "Content-Type", "text/html; charset=" + CHARSET, false);
   3.113 -		addHeader(sb, "Content-Transfer-Encoding", "quoted-printable", false);
   3.114 -		//addHeader(sb, "Content-Transfer-Encoding", "base64", false);
   3.115 -
   3.116 -		Integer parentID = rs.getInt("parent_id");
   3.117 -		if (parentID != null && parentID > 0) {
   3.118 -			String parentMessageID = constructMessageId(parentID, rs.getInt("group_id"), rs.getString("group_name"), myDomain);
   3.119 -			addHeader(sb, "In-Reply-To", parentMessageID, false);
   3.120 -			addHeader(sb, "References", parentMessageID, false);
   3.121 -		}
   3.122 -
   3.123 -		return sb.toString();
   3.124 -	}
   3.125 -
   3.126  	@Override
   3.127  	public List<Group> getGroups() throws StorageBackendException {
   3.128  		PreparedStatement ps = null;
   3.129 @@ -275,13 +208,8 @@
   3.130  			rs = ps.executeQuery();
   3.131  
   3.132  			if (rs.next()) {
   3.133 -				String headers = constructHeaders(rs);
   3.134 -				// TODO: fold?
   3.135 -				BCodec bc = new BCodec(CHARSET);
   3.136 -				byte[] body = qpc.encode(rs.getString("text")).getBytes();
   3.137 -				//byte[] body = bc.encode(rs.getString("text")).getBytes();
   3.138 -
   3.139 -				return new Article(headers, body);
   3.140 +				DrupalMessage m = new DrupalMessage(rs, myDomain, true);
   3.141 +				return new DrupalArticle(m);
   3.142  			} else {
   3.143  				return null;
   3.144  			}
   3.145 @@ -297,7 +225,6 @@
   3.146  		PreparedStatement ps = null;
   3.147  		ResultSet rs = null;
   3.148  		try {
   3.149 -			// TODO: je nutné řazení?
   3.150  			ps = conn.prepareStatement("SELECT * FROM nntp_article WHERE group_id = ? AND id >= ? AND id <= ? ORDER BY id");
   3.151  			ps.setLong(1, group.getInternalID());
   3.152  			ps.setLong(2, first);
   3.153 @@ -307,7 +234,8 @@
   3.154  			List<Pair<Long, ArticleHead>> heads = new ArrayList<Pair<Long, ArticleHead>>();
   3.155  
   3.156  			while (rs.next()) {
   3.157 -				String headers = constructHeaders(rs);
   3.158 +				DrupalMessage m = new DrupalMessage(rs, myDomain, false);
   3.159 +				String headers = m.getHeaders();
   3.160  				heads.add(new Pair<Long, ArticleHead>(rs.getLong("id"), new ArticleHead(headers)));
   3.161  			}
   3.162  
   3.163 @@ -320,13 +248,6 @@
   3.164  	}
   3.165  
   3.166  	@Override
   3.167 -	public List<Pair<Long, String>> getArticleHeaders(Group group, long start, long end, String header, String pattern) throws StorageBackendException {
   3.168 -		log.log(Level.SEVERE, "TODO: getArticleHeaders {0} / {1} / {2} / {3} / {4}", new Object[]{group, start, end, header, pattern});
   3.169 -		/** TODO: */
   3.170 -		return Collections.emptyList();
   3.171 -	}
   3.172 -
   3.173 -	@Override
   3.174  	public long getArticleIndex(Article article, Group group) throws StorageBackendException {
   3.175  		Long id = parseArticleID(article.getMessageID());
   3.176  		if (id == null) {
   3.177 @@ -416,24 +337,6 @@
   3.178  		}
   3.179  	}
   3.180  
   3.181 -	//
   3.182 -	// --- zatím neimplementovat ---
   3.183 -	//
   3.184 -	@Override
   3.185 -	public void addArticle(Article art) throws StorageBackendException {
   3.186 -		log.log(Level.SEVERE, "TODO: addArticle {0}", new Object[]{art});
   3.187 -	}
   3.188 -
   3.189 -	@Override
   3.190 -	public void addEvent(long timestamp, int type, long groupID) throws StorageBackendException {
   3.191 -		log.log(Level.SEVERE, "TODO: addEvent {0} / {1} / {2}", new Object[]{timestamp, type, groupID});
   3.192 -	}
   3.193 -
   3.194 -	@Override
   3.195 -	public void addGroup(String groupname, int flags) throws StorageBackendException {
   3.196 -		log.log(Level.SEVERE, "TODO: addGroup {0} / {1}", new Object[]{groupname, flags});
   3.197 -	}
   3.198 -
   3.199  	@Override
   3.200  	public int countArticles() throws StorageBackendException {
   3.201  		PreparedStatement ps = null;
   3.202 @@ -467,6 +370,45 @@
   3.203  	}
   3.204  
   3.205  	@Override
   3.206 +	public int getPostingsCount(String groupname) throws StorageBackendException {
   3.207 +		PreparedStatement ps = null;
   3.208 +		ResultSet rs = null;
   3.209 +		try {
   3.210 +			ps = conn.prepareStatement("SELECT count(*) FROM nntp_article WHERE group_name = ?");
   3.211 +			ps.setString(1, groupname);
   3.212 +			rs = ps.executeQuery();
   3.213 +			rs.next();
   3.214 +			return rs.getInt(1);
   3.215 +		} catch (Exception e) {
   3.216 +			throw new StorageBackendException(e);
   3.217 +		} finally {
   3.218 +			close(null, ps, rs);
   3.219 +		}
   3.220 +	}
   3.221 +
   3.222 +	@Override
   3.223 +	public List<Pair<Long, String>> getArticleHeaders(Group group, long start, long end, String header, String pattern) throws StorageBackendException {
   3.224 +		log.log(Level.SEVERE, "TODO: getArticleHeaders {0} / {1} / {2} / {3} / {4}", new Object[]{group, start, end, header, pattern});
   3.225 +		/** TODO: */
   3.226 +		return Collections.emptyList();
   3.227 +	}
   3.228 +
   3.229 +	@Override
   3.230 +	public void addArticle(Article art) throws StorageBackendException {
   3.231 +		log.log(Level.SEVERE, "TODO: addArticle {0}", new Object[]{art});
   3.232 +	}
   3.233 +
   3.234 +	@Override
   3.235 +	public void addEvent(long timestamp, int type, long groupID) throws StorageBackendException {
   3.236 +		log.log(Level.SEVERE, "TODO: addEvent {0} / {1} / {2}", new Object[]{timestamp, type, groupID});
   3.237 +	}
   3.238 +
   3.239 +	@Override
   3.240 +	public void addGroup(String groupname, int flags) throws StorageBackendException {
   3.241 +		log.log(Level.SEVERE, "TODO: addGroup {0} / {1}", new Object[]{groupname, flags});
   3.242 +	}
   3.243 +
   3.244 +	@Override
   3.245  	public void delete(String messageID) throws StorageBackendException {
   3.246  		log.log(Level.SEVERE, "TODO: delete {0}", new Object[]{messageID});
   3.247  	}
   3.248 @@ -478,6 +420,11 @@
   3.249  	}
   3.250  
   3.251  	@Override
   3.252 +	public void setConfigValue(String key, String value) throws StorageBackendException {
   3.253 +		log.log(Level.SEVERE, "TODO: setConfigValue {0} = {1}", new Object[]{key, value});
   3.254 +	}
   3.255 +
   3.256 +	@Override
   3.257  	public int getEventsCount(int eventType, long startTimestamp, long endTimestamp, Group group) throws StorageBackendException {
   3.258  		log.log(Level.SEVERE, "TODO: getEventsCount {0} / {1} / {2} / {3}", new Object[]{eventType, startTimestamp, endTimestamp, group});
   3.259  		return 0;
   3.260 @@ -508,23 +455,6 @@
   3.261  	}
   3.262  
   3.263  	@Override
   3.264 -	public int getPostingsCount(String groupname) throws StorageBackendException {
   3.265 -		PreparedStatement ps = null;
   3.266 -		ResultSet rs = null;
   3.267 -		try {
   3.268 -			ps = conn.prepareStatement("SELECT count(*) FROM nntp_article WHERE group_name = ?");
   3.269 -			ps.setString(1, groupname);
   3.270 -			rs = ps.executeQuery();
   3.271 -			rs.next();
   3.272 -			return rs.getInt(1);
   3.273 -		} catch (Exception e) {
   3.274 -			throw new StorageBackendException(e);
   3.275 -		} finally {
   3.276 -			close(null, ps, rs);
   3.277 -		}
   3.278 -	}
   3.279 -
   3.280 -	@Override
   3.281  	public List<Subscription> getSubscriptions(int type) throws StorageBackendException {
   3.282  		log.log(Level.SEVERE, "TODO: getSubscriptions {0}", new Object[]{type});
   3.283  		return Collections.emptyList();
   3.284 @@ -536,11 +466,6 @@
   3.285  	}
   3.286  
   3.287  	@Override
   3.288 -	public void setConfigValue(String key, String value) throws StorageBackendException {
   3.289 -		log.log(Level.SEVERE, "TODO: setConfigValue {0} = {1}", new Object[]{key, value});
   3.290 -	}
   3.291 -
   3.292 -	@Override
   3.293  	public boolean update(Article article) throws StorageBackendException {
   3.294  		log.log(Level.SEVERE, "TODO: update {0}", new Object[]{article});
   3.295  		throw new StorageBackendException("Not implemented yet.");