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.
chris@1
     1
/*
chris@1
     2
 *   SONEWS News Server
chris@1
     3
 *   see AUTHORS for the list of contributors
chris@1
     4
 *
chris@1
     5
 *   This program is free software: you can redistribute it and/or modify
chris@1
     6
 *   it under the terms of the GNU General Public License as published by
chris@1
     7
 *   the Free Software Foundation, either version 3 of the License, or
chris@1
     8
 *   (at your option) any later version.
chris@1
     9
 *
chris@1
    10
 *   This program is distributed in the hope that it will be useful,
chris@1
    11
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
chris@1
    12
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
chris@1
    13
 *   GNU General Public License for more details.
chris@1
    14
 *
chris@1
    15
 *   You should have received a copy of the GNU General Public License
chris@1
    16
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
chris@1
    17
 */
chris@1
    18
chris@1
    19
package org.sonews.daemon.command;
chris@1
    20
chris@1
    21
import java.io.IOException;
chris@1
    22
import java.sql.SQLException;
chris@1
    23
import java.util.List;
chris@1
    24
import org.sonews.util.Log;
chris@1
    25
import org.sonews.daemon.NNTPConnection;
chris@1
    26
import org.sonews.daemon.storage.Article;
chris@1
    27
import org.sonews.daemon.storage.ArticleHead;
chris@1
    28
import org.sonews.daemon.storage.Headers;
chris@1
    29
import org.sonews.util.Pair;
chris@1
    30
chris@1
    31
/**
chris@1
    32
 * Class handling the OVER/XOVER command.
chris@1
    33
 * 
chris@1
    34
 * Description of the XOVER command:
chris@1
    35
 * <pre>
chris@1
    36
 * XOVER [range]
chris@1
    37
 *
chris@1
    38
 * The XOVER command returns information from the overview
chris@1
    39
 * database for the article(s) specified.
chris@1
    40
 *
chris@1
    41
 * The optional range argument may be any of the following:
chris@1
    42
 *              an article number
chris@1
    43
 *              an article number followed by a dash to indicate
chris@1
    44
 *                 all following
chris@1
    45
 *              an article number followed by a dash followed by
chris@1
    46
 *                 another article number
chris@1
    47
 *
chris@1
    48
 * If no argument is specified, then information from the
chris@1
    49
 * current article is displayed. Successful responses start
chris@1
    50
 * with a 224 response followed by the overview information
chris@1
    51
 * for all matched messages. Once the output is complete, a
chris@1
    52
 * period is sent on a line by itself. If no argument is
chris@1
    53
 * specified, the information for the current article is
chris@1
    54
 * returned.  A news group must have been selected earlier,
chris@1
    55
 * else a 412 error response is returned. If no articles are
chris@1
    56
 * in the range specified, a 420 error response is returned
chris@1
    57
 * by the server. A 502 response will be returned if the
chris@1
    58
 * client only has permission to transfer articles.
chris@1
    59
 *
chris@1
    60
 * Each line of output will be formatted with the article number,
chris@1
    61
 * followed by each of the headers in the overview database or the
chris@1
    62
 * article itself (when the data is not available in the overview
chris@1
    63
 * database) for that article separated by a tab character.  The
chris@1
    64
 * sequence of fields must be in this order: subject, author,
chris@1
    65
 * date, message-id, references, byte count, and line count. Other
chris@1
    66
 * optional fields may follow line count. Other optional fields may
chris@1
    67
 * follow line count. These fields are specified by examining the
chris@1
    68
 * response to the LIST OVERVIEW.FMT command. Where no data exists,
chris@1
    69
 * a null field must be provided (i.e. the output will have two tab
chris@1
    70
 * characters adjacent to each other). Servers should not output
chris@1
    71
 * fields for articles that have been removed since the XOVER database
chris@1
    72
 * was created.
chris@1
    73
 *
chris@1
    74
 * The LIST OVERVIEW.FMT command should be implemented if XOVER
chris@1
    75
 * is implemented. A client can use LIST OVERVIEW.FMT to determine
chris@1
    76
 * what optional fields  and in which order all fields will be
chris@1
    77
 * supplied by the XOVER command. 
chris@1
    78
 *
chris@1
    79
 * Note that any tab and end-of-line characters in any header
chris@1
    80
 * data that is returned will be converted to a space character.
chris@1
    81
 *
chris@1
    82
 * Responses:
chris@1
    83
 *
chris@1
    84
 *   224 Overview information follows
chris@1
    85
 *   412 No news group current selected
chris@1
    86
 *   420 No article(s) selected
chris@1
    87
 *   502 no permission
chris@1
    88
 *
chris@1
    89
 * OVER defines additional responses:
chris@1
    90
 *
chris@1
    91
 *  First form (message-id specified)
chris@1
    92
 *    224    Overview information follows (multi-line)
chris@1
    93
 *    430    No article with that message-id
chris@1
    94
 *
chris@1
    95
 *  Second form (range specified)
chris@1
    96
 *    224    Overview information follows (multi-line)
chris@1
    97
 *    412    No newsgroup selected
chris@1
    98
 *    423    No articles in that range
chris@1
    99
 *
chris@1
   100
 *  Third form (current article number used)
chris@1
   101
 *    224    Overview information follows (multi-line)
chris@1
   102
 *    412    No newsgroup selected
chris@1
   103
 *    420    Current article number is invalid
chris@1
   104
 *
chris@1
   105
 * </pre>
chris@1
   106
 * @author Christian Lins
chris@1
   107
 * @since sonews/0.5.0
chris@1
   108
 */
chris@1
   109
public class OverCommand extends AbstractCommand
chris@1
   110
{
chris@1
   111
chris@1
   112
  public static final int MAX_LINES_PER_DBREQUEST = 100;
chris@1
   113
  
chris@1
   114
  public OverCommand(final NNTPConnection conn)
chris@1
   115
  {
chris@1
   116
    super(conn);
chris@1
   117
  }
chris@1
   118
chris@1
   119
  @Override
chris@1
   120
  public boolean hasFinished()
chris@1
   121
  {
chris@1
   122
    return true;
chris@1
   123
  }
chris@1
   124
chris@1
   125
  @Override
chris@1
   126
  public void processLine(final String line)
chris@1
   127
    throws IOException, SQLException
chris@1
   128
  {
chris@1
   129
    if(getCurrentGroup() == null)
chris@1
   130
    {
chris@1
   131
      printStatus(412, "No news group current selected");
chris@1
   132
    }
chris@1
   133
    else
chris@1
   134
    {
chris@1
   135
      String[] command = line.split(" ");
chris@1
   136
chris@1
   137
      // If no parameter was specified, show information about
chris@1
   138
      // the currently selected article(s)
chris@1
   139
      if(command.length == 1)
chris@1
   140
      {
chris@1
   141
        final Article art = getCurrentArticle();
chris@1
   142
        if(art == null)
chris@1
   143
        {
chris@1
   144
          printStatus(420, "No article(s) selected");
chris@1
   145
          return;
chris@1
   146
        }
chris@1
   147
chris@1
   148
        println(buildOverview(art, -1));
chris@1
   149
      }
chris@1
   150
      // otherwise print information about the specified range
chris@1
   151
      else
chris@1
   152
      {
chris@1
   153
        int artStart;
chris@1
   154
        int artEnd   = getCurrentGroup().getLastArticleNumber();
chris@1
   155
        String[] nums = command[1].split("-");
chris@1
   156
        if(nums.length >= 1)
chris@1
   157
        {
chris@1
   158
          try
chris@1
   159
          {
chris@1
   160
            artStart = Integer.parseInt(nums[0]);
chris@1
   161
          }
chris@1
   162
          catch(NumberFormatException e) 
chris@1
   163
          {
chris@1
   164
            Log.msg(e.getMessage(), true);
chris@1
   165
            artStart = Integer.parseInt(command[1]);
chris@1
   166
          }
chris@1
   167
        }
chris@1
   168
        else
chris@1
   169
        {
chris@1
   170
          artStart = getCurrentGroup().getFirstArticleNumber();
chris@1
   171
        }
chris@1
   172
chris@1
   173
        if(nums.length >=2)
chris@1
   174
        {
chris@1
   175
          try
chris@1
   176
          {
chris@1
   177
            artEnd = Integer.parseInt(nums[1]);
chris@1
   178
          }
chris@1
   179
          catch(NumberFormatException e) 
chris@1
   180
          {
chris@1
   181
            e.printStackTrace();
chris@1
   182
          }
chris@1
   183
        }
chris@1
   184
chris@1
   185
        if(artStart > artEnd)
chris@1
   186
        {
chris@1
   187
          if(command[0].equalsIgnoreCase("OVER"))
chris@1
   188
          {
chris@1
   189
            printStatus(423, "No articles in that range");
chris@1
   190
          }
chris@1
   191
          else
chris@1
   192
          {
chris@1
   193
            printStatus(224, "(empty) overview information follows:");
chris@1
   194
            println(".");
chris@1
   195
          }
chris@1
   196
        }
chris@1
   197
        else
chris@1
   198
        {
chris@1
   199
          for(int n = artStart; n <= artEnd; n += MAX_LINES_PER_DBREQUEST)
chris@1
   200
          {
chris@1
   201
            int nEnd = Math.min(n + MAX_LINES_PER_DBREQUEST - 1, artEnd);
chris@1
   202
            List<Pair<Long, ArticleHead>> articleHeads = getCurrentGroup()
chris@1
   203
              .getArticleHeads(n, nEnd);
chris@1
   204
            if(articleHeads.isEmpty() && n == artStart
chris@1
   205
              && command[0].equalsIgnoreCase("OVER"))
chris@1
   206
            {
chris@1
   207
              // This reply is only valid for OVER, not for XOVER command
chris@1
   208
              printStatus(423, "No articles in that range");
chris@1
   209
              return;
chris@1
   210
            }
chris@1
   211
            else if(n == artStart)
chris@1
   212
            {
chris@1
   213
              // XOVER replies this although there is no data available
chris@1
   214
              printStatus(224, "Overview information follows");
chris@1
   215
            }
chris@1
   216
chris@1
   217
            for(Pair<Long, ArticleHead> article : articleHeads)
chris@1
   218
            {
chris@1
   219
              String overview = buildOverview(article.getB(), article.getA());
chris@1
   220
              println(overview);
chris@1
   221
            }
chris@1
   222
          } // for
chris@1
   223
          println(".");
chris@1
   224
        }
chris@1
   225
      }
chris@1
   226
    }
chris@1
   227
  }
chris@1
   228
  
chris@1
   229
  private String buildOverview(ArticleHead art, long nr)
chris@1
   230
  {
chris@1
   231
    StringBuilder overview = new StringBuilder();
chris@1
   232
    overview.append(nr);
chris@1
   233
    overview.append('\t');
chris@1
   234
chris@1
   235
    String subject = art.getHeader(Headers.SUBJECT)[0];
chris@1
   236
    if("".equals(subject))
chris@1
   237
    {
chris@1
   238
      subject = "<empty>";
chris@1
   239
    }
chris@1
   240
    overview.append(escapeString(subject));
chris@1
   241
    overview.append('\t');
chris@1
   242
chris@1
   243
    overview.append(escapeString(art.getHeader(Headers.FROM)[0]));
chris@1
   244
    overview.append('\t');
chris@1
   245
    overview.append(escapeString(art.getHeader(Headers.DATE)[0]));
chris@1
   246
    overview.append('\t');
chris@1
   247
    overview.append(escapeString(art.getHeader(Headers.MESSAGE_ID)[0]));
chris@1
   248
    overview.append('\t');
chris@1
   249
    overview.append(escapeString(art.getHeader(Headers.REFERENCES)[0]));
chris@1
   250
    overview.append('\t');
chris@1
   251
chris@1
   252
    String bytes = art.getHeader(Headers.BYTES)[0];
chris@1
   253
    if("".equals(bytes))
chris@1
   254
    {
chris@1
   255
      bytes = "0";
chris@1
   256
    }
chris@1
   257
    overview.append(escapeString(bytes));
chris@1
   258
    overview.append('\t');
chris@1
   259
chris@1
   260
    String lines = art.getHeader(Headers.LINES)[0];
chris@1
   261
    if("".equals(lines))
chris@1
   262
    {
chris@1
   263
      lines = "0";
chris@1
   264
    }
chris@1
   265
    overview.append(escapeString(lines));
chris@1
   266
    overview.append('\t');
chris@1
   267
    overview.append(escapeString(art.getHeader(Headers.XREF)[0]));
chris@1
   268
chris@1
   269
    // Remove trailing tabs if some data is empty
chris@1
   270
    return overview.toString().trim();
chris@1
   271
  }
chris@1
   272
  
chris@1
   273
  private String escapeString(String str)
chris@1
   274
  {
chris@1
   275
    String nstr = str.replace("\r", "");
chris@1
   276
    nstr = nstr.replace('\n', ' ');
chris@1
   277
    nstr = nstr.replace('\t', ' ');
chris@1
   278
    return nstr.trim();
chris@1
   279
  }
chris@1
   280
  
chris@1
   281
}