diff -r 6ae5e4f8329b -r c404a87db5b7 src/org/sonews/daemon/command/OverCommand.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/sonews/daemon/command/OverCommand.java Sun Aug 29 17:43:58 2010 +0200 @@ -0,0 +1,294 @@ +/* + * SONEWS News Server + * see AUTHORS for the list of contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sonews.daemon.command; + +import java.io.IOException; +import java.util.List; +import org.sonews.util.Log; +import org.sonews.daemon.NNTPConnection; +import org.sonews.storage.Article; +import org.sonews.storage.ArticleHead; +import org.sonews.storage.Headers; +import org.sonews.storage.StorageBackendException; +import org.sonews.util.Pair; + +/** + * Class handling the OVER/XOVER command. + * + * Description of the XOVER command: + *
+ * XOVER [range]
+ *
+ * The XOVER command returns information from the overview
+ * database for the article(s) specified.
+ *
+ * The optional range argument may be any of the following:
+ *              an article number
+ *              an article number followed by a dash to indicate
+ *                 all following
+ *              an article number followed by a dash followed by
+ *                 another article number
+ *
+ * If no argument is specified, then information from the
+ * current article is displayed. Successful responses start
+ * with a 224 response followed by the overview information
+ * for all matched messages. Once the output is complete, a
+ * period is sent on a line by itself. If no argument is
+ * specified, the information for the current article is
+ * returned.  A news group must have been selected earlier,
+ * else a 412 error response is returned. If no articles are
+ * in the range specified, a 420 error response is returned
+ * by the server. A 502 response will be returned if the
+ * client only has permission to transfer articles.
+ *
+ * Each line of output will be formatted with the article number,
+ * followed by each of the headers in the overview database or the
+ * article itself (when the data is not available in the overview
+ * database) for that article separated by a tab character.  The
+ * sequence of fields must be in this order: subject, author,
+ * date, message-id, references, byte count, and line count. Other
+ * optional fields may follow line count. Other optional fields may
+ * follow line count. These fields are specified by examining the
+ * response to the LIST OVERVIEW.FMT command. Where no data exists,
+ * a null field must be provided (i.e. the output will have two tab
+ * characters adjacent to each other). Servers should not output
+ * fields for articles that have been removed since the XOVER database
+ * was created.
+ *
+ * The LIST OVERVIEW.FMT command should be implemented if XOVER
+ * is implemented. A client can use LIST OVERVIEW.FMT to determine
+ * what optional fields  and in which order all fields will be
+ * supplied by the XOVER command. 
+ *
+ * Note that any tab and end-of-line characters in any header
+ * data that is returned will be converted to a space character.
+ *
+ * Responses:
+ *
+ *   224 Overview information follows
+ *   412 No news group current selected
+ *   420 No article(s) selected
+ *   502 no permission
+ *
+ * OVER defines additional responses:
+ *
+ *  First form (message-id specified)
+ *    224    Overview information follows (multi-line)
+ *    430    No article with that message-id
+ *
+ *  Second form (range specified)
+ *    224    Overview information follows (multi-line)
+ *    412    No newsgroup selected
+ *    423    No articles in that range
+ *
+ *  Third form (current article number used)
+ *    224    Overview information follows (multi-line)
+ *    412    No newsgroup selected
+ *    420    Current article number is invalid
+ *
+ * 
+ * @author Christian Lins + * @since sonews/0.5.0 + */ +public class OverCommand implements Command +{ + + public static final int MAX_LINES_PER_DBREQUEST = 200; + + @Override + public String[] getSupportedCommandStrings() + { + return new String[]{"OVER", "XOVER"}; + } + + @Override + public boolean hasFinished() + { + return true; + } + + @Override + public String impliedCapability() + { + return null; + } + + @Override + public boolean isStateful() + { + return false; + } + + @Override + public void processLine(NNTPConnection conn, final String line, byte[] raw) + throws IOException, StorageBackendException + { + if(conn.getCurrentChannel() == null) + { + conn.println("412 no newsgroup selected"); + } + else + { + String[] command = line.split(" "); + + // If no parameter was specified, show information about + // the currently selected article(s) + if(command.length == 1) + { + final Article art = conn.getCurrentArticle(); + if(art == null) + { + conn.println("420 no article(s) selected"); + return; + } + + conn.println(buildOverview(art, -1)); + } + // otherwise print information about the specified range + else + { + long artStart; + long artEnd = conn.getCurrentChannel().getLastArticleNumber(); + String[] nums = command[1].split("-"); + if(nums.length >= 1) + { + try + { + artStart = Integer.parseInt(nums[0]); + } + catch(NumberFormatException e) + { + Log.get().info(e.getMessage()); + artStart = Integer.parseInt(command[1]); + } + } + else + { + artStart = conn.getCurrentChannel().getFirstArticleNumber(); + } + + if(nums.length >=2) + { + try + { + artEnd = Integer.parseInt(nums[1]); + } + catch(NumberFormatException e) + { + e.printStackTrace(); + } + } + + if(artStart > artEnd) + { + if(command[0].equalsIgnoreCase("OVER")) + { + conn.println("423 no articles in that range"); + } + else + { + conn.println("224 (empty) overview information follows:"); + conn.println("."); + } + } + else + { + for(long n = artStart; n <= artEnd; n += MAX_LINES_PER_DBREQUEST) + { + long nEnd = Math.min(n + MAX_LINES_PER_DBREQUEST - 1, artEnd); + List> articleHeads = conn.getCurrentChannel() + .getArticleHeads(n, nEnd); + if(articleHeads.isEmpty() && n == artStart + && command[0].equalsIgnoreCase("OVER")) + { + // This reply is only valid for OVER, not for XOVER command + conn.println("423 no articles in that range"); + return; + } + else if(n == artStart) + { + // XOVER replies this although there is no data available + conn.println("224 overview information follows"); + } + + for(Pair article : articleHeads) + { + String overview = buildOverview(article.getB(), article.getA()); + conn.println(overview); + } + } // for + conn.println("."); + } + } + } + } + + private String buildOverview(ArticleHead art, long nr) + { + StringBuilder overview = new StringBuilder(); + overview.append(nr); + overview.append('\t'); + + String subject = art.getHeader(Headers.SUBJECT)[0]; + if("".equals(subject)) + { + subject = ""; + } + overview.append(escapeString(subject)); + overview.append('\t'); + + overview.append(escapeString(art.getHeader(Headers.FROM)[0])); + overview.append('\t'); + overview.append(escapeString(art.getHeader(Headers.DATE)[0])); + overview.append('\t'); + overview.append(escapeString(art.getHeader(Headers.MESSAGE_ID)[0])); + overview.append('\t'); + overview.append(escapeString(art.getHeader(Headers.REFERENCES)[0])); + overview.append('\t'); + + String bytes = art.getHeader(Headers.BYTES)[0]; + if("".equals(bytes)) + { + bytes = "0"; + } + overview.append(escapeString(bytes)); + overview.append('\t'); + + String lines = art.getHeader(Headers.LINES)[0]; + if("".equals(lines)) + { + lines = "0"; + } + overview.append(escapeString(lines)); + overview.append('\t'); + overview.append(escapeString(art.getHeader(Headers.XREF)[0])); + + // Remove trailing tabs if some data is empty + return overview.toString().trim(); + } + + private String escapeString(String str) + { + String nstr = str.replace("\r", ""); + nstr = nstr.replace('\n', ' '); + nstr = nstr.replace('\t', ' '); + return nstr.trim(); + } + +}