Fix for last commit.
3 * see AUTHORS for the list of contributors
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 package org.sonews.daemon.command;
21 import java.io.IOException;
22 import java.util.List;
23 import org.sonews.util.Log;
24 import org.sonews.daemon.NNTPConnection;
25 import org.sonews.storage.Article;
26 import org.sonews.storage.ArticleHead;
27 import org.sonews.storage.Headers;
28 import org.sonews.storage.StorageBackendException;
29 import org.sonews.util.Pair;
32 * Class handling the OVER/XOVER command.
34 * Description of the XOVER command:
38 * The XOVER command returns information from the overview
39 * database for the article(s) specified.
41 * The optional range argument may be any of the following:
43 * an article number followed by a dash to indicate
45 * an article number followed by a dash followed by
46 * another article number
48 * If no argument is specified, then information from the
49 * current article is displayed. Successful responses start
50 * with a 224 response followed by the overview information
51 * for all matched messages. Once the output is complete, a
52 * period is sent on a line by itself. If no argument is
53 * specified, the information for the current article is
54 * returned. A news group must have been selected earlier,
55 * else a 412 error response is returned. If no articles are
56 * in the range specified, a 420 error response is returned
57 * by the server. A 502 response will be returned if the
58 * client only has permission to transfer articles.
60 * Each line of output will be formatted with the article number,
61 * followed by each of the headers in the overview database or the
62 * article itself (when the data is not available in the overview
63 * database) for that article separated by a tab character. The
64 * sequence of fields must be in this order: subject, author,
65 * date, message-id, references, byte count, and line count. Other
66 * optional fields may follow line count. Other optional fields may
67 * follow line count. These fields are specified by examining the
68 * response to the LIST OVERVIEW.FMT command. Where no data exists,
69 * a null field must be provided (i.e. the output will have two tab
70 * characters adjacent to each other). Servers should not output
71 * fields for articles that have been removed since the XOVER database
74 * The LIST OVERVIEW.FMT command should be implemented if XOVER
75 * is implemented. A client can use LIST OVERVIEW.FMT to determine
76 * what optional fields and in which order all fields will be
77 * supplied by the XOVER command.
79 * Note that any tab and end-of-line characters in any header
80 * data that is returned will be converted to a space character.
84 * 224 Overview information follows
85 * 412 No news group current selected
86 * 420 No article(s) selected
89 * OVER defines additional responses:
91 * First form (message-id specified)
92 * 224 Overview information follows (multi-line)
93 * 430 No article with that message-id
95 * Second form (range specified)
96 * 224 Overview information follows (multi-line)
97 * 412 No newsgroup selected
98 * 423 No articles in that range
100 * Third form (current article number used)
101 * 224 Overview information follows (multi-line)
102 * 412 No newsgroup selected
103 * 420 Current article number is invalid
106 * @author Christian Lins
107 * @since sonews/0.5.0
109 public class OverCommand implements Command
112 public static final int MAX_LINES_PER_DBREQUEST = 200;
115 public String[] getSupportedCommandStrings()
117 return new String[]{"OVER", "XOVER"};
121 public boolean hasFinished()
127 public boolean isStateful()
133 public void processLine(NNTPConnection conn, final String line, byte[] raw)
134 throws IOException, StorageBackendException
136 if(conn.getCurrentChannel() == null)
138 conn.println("412 no newsgroup selected");
142 String[] command = line.split(" ");
144 // If no parameter was specified, show information about
145 // the currently selected article(s)
146 if(command.length == 1)
148 final Article art = conn.getCurrentArticle();
151 conn.println("420 no article(s) selected");
155 conn.println(buildOverview(art, -1));
157 // otherwise print information about the specified range
161 long artEnd = conn.getCurrentChannel().getLastArticleNumber();
162 String[] nums = command[1].split("-");
167 artStart = Integer.parseInt(nums[0]);
169 catch(NumberFormatException e)
171 Log.get().info(e.getMessage());
172 artStart = Integer.parseInt(command[1]);
177 artStart = conn.getCurrentChannel().getFirstArticleNumber();
184 artEnd = Integer.parseInt(nums[1]);
186 catch(NumberFormatException e)
192 if(artStart > artEnd)
194 if(command[0].equalsIgnoreCase("OVER"))
196 conn.println("423 no articles in that range");
200 conn.println("224 (empty) overview information follows:");
206 for(long n = artStart; n <= artEnd; n += MAX_LINES_PER_DBREQUEST)
208 long nEnd = Math.min(n + MAX_LINES_PER_DBREQUEST - 1, artEnd);
209 List<Pair<Long, ArticleHead>> articleHeads = conn.getCurrentChannel()
210 .getArticleHeads(n, nEnd);
211 if(articleHeads.isEmpty() && n == artStart
212 && command[0].equalsIgnoreCase("OVER"))
214 // This reply is only valid for OVER, not for XOVER command
215 conn.println("423 no articles in that range");
218 else if(n == artStart)
220 // XOVER replies this although there is no data available
221 conn.println("224 overview information follows");
224 for(Pair<Long, ArticleHead> article : articleHeads)
226 String overview = buildOverview(article.getB(), article.getA());
227 conn.println(overview);
236 private String buildOverview(ArticleHead art, long nr)
238 StringBuilder overview = new StringBuilder();
240 overview.append('\t');
242 String subject = art.getHeader(Headers.SUBJECT)[0];
243 if("".equals(subject))
247 overview.append(escapeString(subject));
248 overview.append('\t');
250 overview.append(escapeString(art.getHeader(Headers.FROM)[0]));
251 overview.append('\t');
252 overview.append(escapeString(art.getHeader(Headers.DATE)[0]));
253 overview.append('\t');
254 overview.append(escapeString(art.getHeader(Headers.MESSAGE_ID)[0]));
255 overview.append('\t');
256 overview.append(escapeString(art.getHeader(Headers.REFERENCES)[0]));
257 overview.append('\t');
259 String bytes = art.getHeader(Headers.BYTES)[0];
264 overview.append(escapeString(bytes));
265 overview.append('\t');
267 String lines = art.getHeader(Headers.LINES)[0];
272 overview.append(escapeString(lines));
273 overview.append('\t');
274 overview.append(escapeString(art.getHeader(Headers.XREF)[0]));
276 // Remove trailing tabs if some data is empty
277 return overview.toString().trim();
280 private String escapeString(String str)
282 String nstr = str.replace("\r", "");
283 nstr = nstr.replace('\n', ' ');
284 nstr = nstr.replace('\t', ' ');