org/sonews/daemon/command/OverCommand.java
author chris <chris@marvin>
Fri Jun 26 16:48:50 2009 +0200 (2009-06-26)
changeset 1 6fceb66e1ad7
child 3 2fdc9cc89502
permissions -rw-r--r--
Hooray... sonews/0.5.0 final

HG: Enter commit message. Lines beginning with 'HG:' are removed.
HG: Remove all lines to abort the collapse operation.
     1 /*
     2  *   SONEWS News Server
     3  *   see AUTHORS for the list of contributors
     4  *
     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.
     9  *
    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.
    14  *
    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/>.
    17  */
    18 
    19 package org.sonews.daemon.command;
    20 
    21 import java.io.IOException;
    22 import java.sql.SQLException;
    23 import java.util.List;
    24 import org.sonews.util.Log;
    25 import org.sonews.daemon.NNTPConnection;
    26 import org.sonews.daemon.storage.Article;
    27 import org.sonews.daemon.storage.ArticleHead;
    28 import org.sonews.daemon.storage.Headers;
    29 import org.sonews.util.Pair;
    30 
    31 /**
    32  * Class handling the OVER/XOVER command.
    33  * 
    34  * Description of the XOVER command:
    35  * <pre>
    36  * XOVER [range]
    37  *
    38  * The XOVER command returns information from the overview
    39  * database for the article(s) specified.
    40  *
    41  * The optional range argument may be any of the following:
    42  *              an article number
    43  *              an article number followed by a dash to indicate
    44  *                 all following
    45  *              an article number followed by a dash followed by
    46  *                 another article number
    47  *
    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.
    59  *
    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
    72  * was created.
    73  *
    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. 
    78  *
    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.
    81  *
    82  * Responses:
    83  *
    84  *   224 Overview information follows
    85  *   412 No news group current selected
    86  *   420 No article(s) selected
    87  *   502 no permission
    88  *
    89  * OVER defines additional responses:
    90  *
    91  *  First form (message-id specified)
    92  *    224    Overview information follows (multi-line)
    93  *    430    No article with that message-id
    94  *
    95  *  Second form (range specified)
    96  *    224    Overview information follows (multi-line)
    97  *    412    No newsgroup selected
    98  *    423    No articles in that range
    99  *
   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
   104  *
   105  * </pre>
   106  * @author Christian Lins
   107  * @since sonews/0.5.0
   108  */
   109 public class OverCommand extends AbstractCommand
   110 {
   111 
   112   public static final int MAX_LINES_PER_DBREQUEST = 100;
   113   
   114   public OverCommand(final NNTPConnection conn)
   115   {
   116     super(conn);
   117   }
   118 
   119   @Override
   120   public boolean hasFinished()
   121   {
   122     return true;
   123   }
   124 
   125   @Override
   126   public void processLine(final String line)
   127     throws IOException, SQLException
   128   {
   129     if(getCurrentGroup() == null)
   130     {
   131       printStatus(412, "No news group current selected");
   132     }
   133     else
   134     {
   135       String[] command = line.split(" ");
   136 
   137       // If no parameter was specified, show information about
   138       // the currently selected article(s)
   139       if(command.length == 1)
   140       {
   141         final Article art = getCurrentArticle();
   142         if(art == null)
   143         {
   144           printStatus(420, "No article(s) selected");
   145           return;
   146         }
   147 
   148         println(buildOverview(art, -1));
   149       }
   150       // otherwise print information about the specified range
   151       else
   152       {
   153         int artStart;
   154         int artEnd   = getCurrentGroup().getLastArticleNumber();
   155         String[] nums = command[1].split("-");
   156         if(nums.length >= 1)
   157         {
   158           try
   159           {
   160             artStart = Integer.parseInt(nums[0]);
   161           }
   162           catch(NumberFormatException e) 
   163           {
   164             Log.msg(e.getMessage(), true);
   165             artStart = Integer.parseInt(command[1]);
   166           }
   167         }
   168         else
   169         {
   170           artStart = getCurrentGroup().getFirstArticleNumber();
   171         }
   172 
   173         if(nums.length >=2)
   174         {
   175           try
   176           {
   177             artEnd = Integer.parseInt(nums[1]);
   178           }
   179           catch(NumberFormatException e) 
   180           {
   181             e.printStackTrace();
   182           }
   183         }
   184 
   185         if(artStart > artEnd)
   186         {
   187           if(command[0].equalsIgnoreCase("OVER"))
   188           {
   189             printStatus(423, "No articles in that range");
   190           }
   191           else
   192           {
   193             printStatus(224, "(empty) overview information follows:");
   194             println(".");
   195           }
   196         }
   197         else
   198         {
   199           for(int n = artStart; n <= artEnd; n += MAX_LINES_PER_DBREQUEST)
   200           {
   201             int nEnd = Math.min(n + MAX_LINES_PER_DBREQUEST - 1, artEnd);
   202             List<Pair<Long, ArticleHead>> articleHeads = getCurrentGroup()
   203               .getArticleHeads(n, nEnd);
   204             if(articleHeads.isEmpty() && n == artStart
   205               && command[0].equalsIgnoreCase("OVER"))
   206             {
   207               // This reply is only valid for OVER, not for XOVER command
   208               printStatus(423, "No articles in that range");
   209               return;
   210             }
   211             else if(n == artStart)
   212             {
   213               // XOVER replies this although there is no data available
   214               printStatus(224, "Overview information follows");
   215             }
   216 
   217             for(Pair<Long, ArticleHead> article : articleHeads)
   218             {
   219               String overview = buildOverview(article.getB(), article.getA());
   220               println(overview);
   221             }
   222           } // for
   223           println(".");
   224         }
   225       }
   226     }
   227   }
   228   
   229   private String buildOverview(ArticleHead art, long nr)
   230   {
   231     StringBuilder overview = new StringBuilder();
   232     overview.append(nr);
   233     overview.append('\t');
   234 
   235     String subject = art.getHeader(Headers.SUBJECT)[0];
   236     if("".equals(subject))
   237     {
   238       subject = "<empty>";
   239     }
   240     overview.append(escapeString(subject));
   241     overview.append('\t');
   242 
   243     overview.append(escapeString(art.getHeader(Headers.FROM)[0]));
   244     overview.append('\t');
   245     overview.append(escapeString(art.getHeader(Headers.DATE)[0]));
   246     overview.append('\t');
   247     overview.append(escapeString(art.getHeader(Headers.MESSAGE_ID)[0]));
   248     overview.append('\t');
   249     overview.append(escapeString(art.getHeader(Headers.REFERENCES)[0]));
   250     overview.append('\t');
   251 
   252     String bytes = art.getHeader(Headers.BYTES)[0];
   253     if("".equals(bytes))
   254     {
   255       bytes = "0";
   256     }
   257     overview.append(escapeString(bytes));
   258     overview.append('\t');
   259 
   260     String lines = art.getHeader(Headers.LINES)[0];
   261     if("".equals(lines))
   262     {
   263       lines = "0";
   264     }
   265     overview.append(escapeString(lines));
   266     overview.append('\t');
   267     overview.append(escapeString(art.getHeader(Headers.XREF)[0]));
   268 
   269     // Remove trailing tabs if some data is empty
   270     return overview.toString().trim();
   271   }
   272   
   273   private String escapeString(String str)
   274   {
   275     String nstr = str.replace("\r", "");
   276     nstr = nstr.replace('\n', ' ');
   277     nstr = nstr.replace('\t', ' ');
   278     return nstr.trim();
   279   }
   280   
   281 }