gecko-dev/grendel/dog/mail/nntp/NNTPStore.java
1999-06-22 10:11:56 +00:00

1022 lines
31 KiB
Java

/*
* NNTPStore.java
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is Knife.
*
* The Initial Developer of the Original Code is dog.
* Portions created by dog are Copyright (C) 1998 dog <dog@dog.net.uk>. All Rights Reserved.
*
* Contributor(s): Edwin Woudt <edwin@woudt.nl>
*/
package dog.mail.nntp;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.mail.*;
import javax.mail.event.*;
import javax.mail.internet.*;
import dog.mail.util.*;
import dog.util.*;
/**
* The storage class implementing the NNTP Usenet news protocol.
*
* @author dog@dog.net.uk
* @version 1.02
*/
public class NNTPStore extends Store implements StatusSource {
/**
* The default NNTP port.
*/
public static final int DEFAULT_PORT = 119;
static int fetchsize = 1024;
Socket socket;
CRLFInputStream in;
CRLFOutputStream out;
String hostname;
String response;
static final int HELP = 100;
static final int READY = 200;
static final int READ_ONLY = 201;
static final int STREAMING_OK = 203;
static final int CLOSING = 205;
static final int GROUP_SELECTED = 211; // or list of article numbers follows
static final int LISTING = 215; // list of newsgroups follows
static final int ARTICLE_RETRIEVED_BOTH = 220;
static final int ARTICLE_RETRIEVED_HEAD = 221; // or header follows
static final int ARTICLE_RETRIEVED_BODY = 222;
static final int ARTICLE_RETRIEVED = 223;
static final int LISTING_OVERVIEW = 224;
static final int LISTING_ARTICLES = 230; // list of new articles by message-id follows
static final int LISTING_NEW = 231; // list of new newsgroups follows
static final int ARTICLE_POSTED = 240; // article posted ok
static final int SEND_ARTICLE = 340; // send article to be posted. End with <CR-LF>.<CR-LF>
static final int SERVICE_DISCONTINUED = 400;
static final int NO_SUCH_GROUP = 411;
static final int NO_GROUP_SELECTED = 412; // no newsgroup has been selected
static final int NO_ARTICLE_SELECTED = 420; // no current article has been selected
static final int NO_SUCH_ARTICLE_IN_GROUP = 423; // no such article number in this group
static final int NO_SUCH_ARTICLE = 430; // no such article found
static final int POSTING_NOT_ALLOWED = 440; // posting not allowed
static final int POSTING_FAILED = 441; // posting failed
static final int COMMAND_NOT_RECOGNIZED = 500;
static final int COMMAND_SYNTAX_ERROR = 501;
static final int PERMISSION_DENIED = 502;
static final int SERVER_ERROR = 503;
boolean postingAllowed = false;
Root root;
Newsgroup current;
Date lastNewGroup;
Hashtable newsgroups = new Hashtable(); // hashtable of newsgroups by name
Hashtable articles = new Hashtable(); // hashtable of articles by message-id
Vector statusListeners = new Vector();
/**
* Constructor.
*/
public NNTPStore(Session session, URLName urlname) {
super(session, urlname);
String ccs = session.getProperty("mail.nntp.fetchsize");
if (ccs!=null) try { fetchsize = Math.max(Integer.parseInt(ccs), 1024); } catch (NumberFormatException e) {}
}
/**
* Connects to the NNTP server and authenticates with the specified parameters.
*/
protected boolean protocolConnect(String host, int port, String username, String password) throws MessagingException {
if (port<0) port = DEFAULT_PORT;
if (host==null)
return false;
try {
hostname = host;
socket = new Socket(host, port);
in = new CRLFInputStream(new BufferedInputStream(socket.getInputStream()));
out = new CRLFOutputStream(new BufferedOutputStream(socket.getOutputStream()));
switch (getResponse()) {
case READY:
postingAllowed = true;
case READ_ONLY:
//StringTokenizer st = new StringTokenizer(response);
//if (st.hasMoreTokens()) hostname = st.nextToken();
break;
default:
throw new MessagingException("unexpected server response: "+response);
}
send("MODE READER"); // newsreader extension
switch (getResponse()) {
case READY:
postingAllowed = true;
case READ_ONLY:
break;
}
readNewsrc();
return true;
} catch(UnknownHostException e) {
throw new MessagingException("unknown host", e);
} catch(IOException e) {
throw new MessagingException("I/O error", e);
}
}
/**
* Closes the connection.
*/
public synchronized void close() throws MessagingException {
if (socket!=null)
try {
send("QUIT");
switch (getResponse()) {
case CLOSING:
break;
case SERVER_ERROR:
if (response.toLowerCase().indexOf("timeout")>-1)
break;
default:
throw new MessagingException("unexpected server response: "+response);
}
socket.close();
socket = null;
} catch (IOException e) {
// socket.close() seems to throw an exception!
//throw new MessagingException("Close failed", e);
}
super.close();
}
/**
* Returns the hostname of the server.
*/
public String getHostName() { return hostname; }
int getResponse() throws IOException {
response = in.readLine();
try {
int index = response.indexOf(' ');
int code = Integer.parseInt(response.substring(0, index));
response = response.substring(index+1);
return code;
} catch (StringIndexOutOfBoundsException e) {
throw new ProtocolException("NNTP protocol exception: "+response);
} catch (NumberFormatException e) {
throw new ProtocolException("NNTP protocol exception: "+response);
}
}
void send(String command) throws IOException {
out.write(command.getBytes());
out.writeln();
out.flush();
}
// Opens a newsgroup.
synchronized void open(Newsgroup group) throws MessagingException {
if (current!=null) {
if (current.equals(group))
return;
else
close(current);
}
String name = group.getName();
try {
send("GROUP "+name);
int r = getResponse();
switch (r) {
case GROUP_SELECTED:
try {
updateGroup(group, response);
group.open = true;
current = group;
} catch (NumberFormatException e) {
throw new MessagingException("NNTP protocol exception: "+response, e);
}
break;
case NO_SUCH_GROUP:
throw new MessagingException(response);
case SERVER_ERROR:
if (response.toLowerCase().indexOf("timeout")>-1) {
close();
connect();
open(group);
break;
}
default:
throw new MessagingException("unexpected server response ("+r+"): "+response);
}
} catch (IOException e) {
throw new MessagingException("I/O error", e);
}
}
// Updates a newsgroup with the most recent article counts.
void updateGroup(Newsgroup newsgroup, String response) throws IOException {
try {
StringTokenizer st = new StringTokenizer(response, " ");
newsgroup.count = Integer.parseInt(st.nextToken());
newsgroup.first = Integer.parseInt(st.nextToken());
newsgroup.last = Integer.parseInt(st.nextToken());
} catch (NumberFormatException e) {
throw new ProtocolException("NNTP protocol exception");
} catch (NoSuchElementException e) {
throw new ProtocolException("NNTP protocol exception");
}
}
// Closes a newsgroup.
synchronized void close(Newsgroup group) throws MessagingException {
if (current!=null && !current.equals(group)) close(current);
group.open = false;
}
// Returns the (approximate) number of articles in a newsgroup.
synchronized int getMessageCount(Newsgroup group) throws MessagingException {
String name = group.getName();
try {
send("GROUP "+name);
switch (getResponse()) {
case GROUP_SELECTED:
try {
updateGroup(group, response);
current = group;
return group.count;
} catch(NumberFormatException e) {
throw new MessagingException("NNTP protocol exception: "+response, e);
}
case NO_SUCH_GROUP:
throw new MessagingException("No such group");
case SERVER_ERROR:
if (response.toLowerCase().indexOf("timeout")>-1) {
close();
connect();
return getMessageCount(group);
}
default:
throw new MessagingException("unexpected server response: "+response);
}
} catch (IOException e) {
throw new MessagingException("I/O error", e);
}
}
// Returns the headers for an article.
synchronized InternetHeaders getHeaders(Article article) throws MessagingException {
String mid = article.messageId;
if (mid==null) {
Newsgroup group = (Newsgroup)article.getFolder();
if (!current.equals(group)) open(group);
mid = Integer.toString(article.getMessageNumber());
}
try {
send("HEAD "+mid);
switch (getResponse()) {
case ARTICLE_RETRIEVED_HEAD:
return new InternetHeaders(new MessageInputStream(in));
case NO_GROUP_SELECTED:
throw new MessagingException("No group selected");
case NO_ARTICLE_SELECTED:
throw new MessagingException("No article selected");
case NO_SUCH_ARTICLE_IN_GROUP:
throw new MessagingException("No such article in group");
case NO_SUCH_ARTICLE:
throw new MessagingException("No such article");
case SERVER_ERROR:
if (response.toLowerCase().indexOf("timeout")>-1) {
close();
connect();
return getHeaders(article);
}
default:
throw new MessagingException("unexpected server response: "+response);
}
} catch (IOException e) {
throw new MessagingException("I/O error", e);
}
}
// Returns the content for an article.
synchronized byte[] getContent(Article article) throws MessagingException {
String mid = article.messageId;
if (mid==null) {
Newsgroup group = (Newsgroup)article.getFolder();
if (!current.equals(group)) open(group);
mid = Integer.toString(article.getMessageNumber());
}
try {
send("BODY "+mid);
switch (getResponse()) {
case ARTICLE_RETRIEVED_BODY:
int max = fetchsize, len;
byte b[] = new byte[max];
MessageInputStream min = new MessageInputStream(in);
ByteArrayOutputStream bout = new ByteArrayOutputStream();
while ((len = min.read(b, 0, max))!=-1)
bout.write(b, 0, len);
return bout.toByteArray();
case NO_GROUP_SELECTED:
throw new MessagingException("No group selected");
case NO_ARTICLE_SELECTED:
throw new MessagingException("No article selected");
case NO_SUCH_ARTICLE_IN_GROUP:
throw new MessagingException("No such article in group");
case NO_SUCH_ARTICLE:
throw new MessagingException("No such article");
case SERVER_ERROR:
if (response.toLowerCase().indexOf("timeout")>-1) {
close();
connect();
return getContent(article);
}
default:
throw new MessagingException("unexpected server response: "+response);
}
} catch (IOException e) {
throw new MessagingException("I/O error", e);
}
}
/**
* Post an article.
* @param article the article
* @param addresses the addresses to post to.
* @exception MessagingException if a messaging exception occurred or there were no newsgroup recipients
*/
public void postArticle(Message article, Address[] addresses) throws MessagingException {
Vector v = new Vector();
for (int i=0; i<addresses.length; i++) { // get unique newsgroup addresses
if (addresses[i] instanceof NewsAddress && !v.contains(addresses[i]))
v.addElement(addresses[i]);
}
NewsAddress[] a = new NewsAddress[v.size()]; v.copyInto(a);
if (a.length==0) throw new MessagingException("No newsgroups specified as recipients");
for (int i=0; i<a.length; i++)
post(article, a[i]);
}
/**
* Returns a Transport that can be used to send articles to this news server.
*/
public Transport getTransport() {
return this.new NNTPTransport(session, url);
}
// Posts an article to the specified newsgroup.
synchronized void post(Message article, NewsAddress address) throws MessagingException {
String group = address.getNewsgroup();
try {
send("POST");
switch (getResponse()) {
case SEND_ARTICLE:
MessageOutputStream mout = new MessageOutputStream(out);
article.writeTo(mout);
out.write("\n.\n".getBytes());
out.flush();
switch (getResponse()) {
case ARTICLE_POSTED:
break;
case POSTING_FAILED:
throw new MessagingException("Posting failed: "+response);
default:
throw new MessagingException(response);
}
break;
case POSTING_NOT_ALLOWED:
throw new MessagingException("Posting not allowed");
case POSTING_FAILED:
throw new MessagingException("Posting failed");
case SERVER_ERROR:
if (response.toLowerCase().indexOf("timeout")>-1) {
connect();
post(article, address);
break;
}
default:
throw new MessagingException("unexpected server response: "+response);
}
} catch (IOException e) {
throw new MessagingException("I/O error", e);
}
}
// Attempts to discover which newsgroups exist and which articles have been read.
void readNewsrc() {
try {
File newsrc;
String osname = System.getProperties().getProperty("os.name");
if (osname.startsWith("Windows") || osname.startsWith("Win32") ||
osname.startsWith("Win16") || osname.startsWith("16-bit Windows")) {
newsrc = new File(System.getProperty("user.home")+File.separator+"news-"+getHostName()+".rc");
if (!newsrc.exists())
newsrc = new File(System.getProperty("user.home")+File.separator+"news.rc");
} else {
newsrc = new File(System.getProperty("user.home")+File.separator+".newsrc-"+getHostName());
if (!newsrc.exists())
newsrc = new File(System.getProperty("user.home")+File.separator+".newsrc");
}
BufferedReader reader = new BufferedReader(new FileReader(newsrc));
String line;
while ((line = reader.readLine())!=null) {
StringTokenizer st = new StringTokenizer(line, ":!", true);
try {
String name = st.nextToken();
Newsgroup group = new Newsgroup(this, name);
group.subscribed = ":".equals(st.nextToken());
if (st.hasMoreTokens())
group.newsrcline = st.nextToken().trim();
newsgroups.put(name, group);
} catch (NoSuchElementException e) {}
}
} catch (FileNotFoundException e) {
} catch (IOException e) {
} catch (SecurityException e) { // not allowed to read file
}
}
// Returns the newsgroups available in this store via the specified listing command.
Newsgroup[] getNewsgroups(String command, boolean retry) throws MessagingException {
Vector vector = new Vector();
try {
send(command);
switch (getResponse()) {
case LISTING:
String line;
for (line=in.readLine(); line!=null && !".".equals(line); line = in.readLine()) {
StringTokenizer st = new StringTokenizer(line, " ");
String name = st.nextToken();
int last = Integer.parseInt(st.nextToken());
int first = Integer.parseInt(st.nextToken());
boolean posting = ("y".equals(st.nextToken().toLowerCase()));
Newsgroup group = (Newsgroup)getFolder(name);
group.first = first;
group.last = last;
group.postingAllowed = posting;
vector.addElement(group);
}
break;
case SERVER_ERROR:
if (!retry) {
if (response.toLowerCase().indexOf("timeout")>-1) {
close();
connect();
return getNewsgroups(command, true);
} else
return getNewsgroups("LIST", false); // backward compatibility with rfc977
}
default:
throw new MessagingException(command+" failed: "+response);
}
} catch (IOException e) {
throw new MessagingException(command+" failed", e);
} catch (NumberFormatException e) {
throw new MessagingException(command+" failed", e);
}
Newsgroup[] groups = new Newsgroup[vector.size()]; vector.copyInto(groups);
return groups;
}
// Returns the articles for a newsgroup.
Article[] getArticles(Newsgroup newsgroup) throws MessagingException {
try {
return getOverview(newsgroup);
} catch (MessagingException e) { // try rfc977
return getNewArticles(newsgroup, new Date(0L));
}
}
/**
* Returns the newsgroups added to this store since the specified date.
* @exception MessagingException if a messaging error occurred
*/
Newsgroup[] getNewFolders(Date date) throws MessagingException {
String datetime = getDateTimeString(date);
Vector vector = new Vector();
try {
send("NEWGROUPS "+datetime);
switch (getResponse()) {
case LISTING_NEW:
String line;
for (line=in.readLine(); line!=null && !".".equals(line); line = in.readLine()) {
StringTokenizer st = new StringTokenizer(line, " ");
String name = st.nextToken();
int last = Integer.parseInt(st.nextToken());
int first = Integer.parseInt(st.nextToken());
boolean posting = ("y".equals(st.nextToken().toLowerCase()));
Newsgroup group = (Newsgroup)getFolder(name);
group.first = first;
group.last = last;
group.postingAllowed = posting;
vector.addElement(group);
}
break;
default:
throw new MessagingException("Listing failed: "+response);
}
} catch (IOException e) {
throw new MessagingException("Listing failed", e);
} catch (NumberFormatException e) {
throw new MessagingException("Listing failed", e);
}
Newsgroup[] groups = new Newsgroup[vector.size()]; vector.copyInto(groups);
return groups;
}
// Returns the articles added to the specified newsgroup since the specified date.
Article[] getNewArticles(Newsgroup newsgroup, Date date) throws MessagingException {
String command = "NEWNEWS "+newsgroup.getName()+" "+getDateTimeString(date);
Vector vector = new Vector();
try {
send(command);
switch (getResponse()) {
case LISTING_ARTICLES:
String line;
for (line=in.readLine(); line!=null && !".".equals(line); line = in.readLine()) {
Message article = getArticle(newsgroup, line);
vector.addElement(article);
}
break;
default:
throw new MessagingException(command+" failed: "+response);
}
} catch (IOException e) {
throw new MessagingException(command+" failed", e);
} catch (NumberFormatException e) {
throw new MessagingException(command+" failed", e);
}
Article[] articles = new Article[vector.size()]; vector.copyInto(articles);
return articles;
}
// Returns a GMT date-time formatted string for the specified date,
// suitable as an argument to NEWGROUPS and NEWNEWS commands.
String getDateTimeString(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeZone(TimeZone.getTimeZone("GMT"));
calendar.setTime(date);
StringBuffer buffer = new StringBuffer();
int field;
String ZERO = "0"; // leading zero
field = calendar.get(Calendar.YEAR)%100; buffer.append((field<10) ? ZERO+field : Integer.toString(field));
field = calendar.get(Calendar.MONTH)+1; buffer.append((field<10) ? ZERO+field : Integer.toString(field));
field = calendar.get(Calendar.DAY_OF_MONTH); buffer.append((field<10) ? ZERO+field : Integer.toString(field));
buffer.append(" ");
field = calendar.get(Calendar.HOUR_OF_DAY); buffer.append((field<10) ? ZERO+field : Integer.toString(field));
field = calendar.get(Calendar.MINUTE); buffer.append((field<10) ? ZERO+field : Integer.toString(field));
field = calendar.get(Calendar.SECOND); buffer.append((field<10) ? ZERO+field : Integer.toString(field));
buffer.append(" GMT");
return buffer.toString();
}
/**
* Returns the root folder.
*/
public Folder getDefaultFolder() throws MessagingException {
synchronized (this) {
if (root==null) root = new Root(this);
}
return root;
}
/**
* Returns the newsgroup with the specified name.
*/
public Folder getFolder(String name) throws MessagingException {
return getNewsgroup(name);
}
/**
* Returns the newsgroup specified as part of a URLName.
*/
public Folder getFolder(URLName urlname) throws MessagingException {
String group = urlname.getFile();
int hashIndex = group.indexOf('#');
if (hashIndex>-1) group = group.substring(0, hashIndex);
return getNewsgroup(group);
}
Newsgroup getNewsgroup(String name) {
Newsgroup newsgroup = (Newsgroup)newsgroups.get(name);
if (newsgroup==null) {
newsgroup = new Newsgroup(this, name);
newsgroups.put(name, newsgroup);
}
return newsgroup;
}
// Returns the article with the specified message-id for the newsgroup.
Article getArticle(Newsgroup newsgroup, String mid) throws MessagingException {
Article article = (Article)articles.get(mid);
if (article==null) {
article = new Article(newsgroup, mid);
articles.put(mid, article);
}
return article;
}
// -- NNTP extensions --
int[] getArticleNumbers(Newsgroup newsgroup) throws MessagingException {
String command = "LISTGROUP "+newsgroup.getName();
Vector vector = new Vector();
synchronized (this) {
try {
send(command);
switch (getResponse()) {
case GROUP_SELECTED:
String line;
for (line=in.readLine(); line!=null && !".".equals(line); line = in.readLine()) {
vector.addElement(line);
}
break;
case NO_GROUP_SELECTED:
case PERMISSION_DENIED:
default:
throw new MessagingException(command+" failed: "+response);
}
} catch (IOException e) {
throw new MessagingException(command+" failed", e);
} catch (NumberFormatException e) {
throw new MessagingException(command+" failed", e);
}
}
int[] numbers = new int[vector.size()];
for (int i=0; i<numbers.length; i++)
numbers[i] = Integer.parseInt((String)vector.elementAt(i));
return numbers;
}
String[] overviewFormat;
String[] getOverviewFormat() throws MessagingException {
if (overviewFormat!=null) return overviewFormat;
String command = "LIST OVERVIEW.FMT";
synchronized (this) {
Vector vector = new Vector();
try {
send(command);
switch (getResponse()) {
case LISTING:
String line;
for (line=in.readLine(); line!=null && !".".equals(line); line = in.readLine()) {
vector.addElement(line);
}
break;
case SERVER_ERROR:
default:
throw new MessagingException(command+" failed: "+response);
}
} catch (IOException e) {
throw new MessagingException(command+" failed", e);
} catch (NumberFormatException e) {
throw new MessagingException(command+" failed", e);
}
overviewFormat = new String[vector.size()]; vector.copyInto(overviewFormat);
return overviewFormat;
}
}
// Prefetches header information for the specified messages, if possible
Article[] getOverview(Newsgroup newsgroup) throws MessagingException {
String name = newsgroup.getName();
String[] format = getOverviewFormat();
String command = "GROUP "+name;
if (current==null || !name.equals(current.getName())) { // select the group
synchronized (this) {
try {
send(command);
switch (getResponse()) {
case GROUP_SELECTED:
try {
updateGroup(newsgroup, response);
current = newsgroup;
} catch(NumberFormatException e) {
throw new MessagingException("NNTP protocol exception: "+response, e);
}
case NO_SUCH_GROUP:
throw new MessagingException("No such group");
case SERVER_ERROR:
if (response.toLowerCase().indexOf("timeout")>-1) {
close();
connect();
return getOverview(newsgroup);
}
default:
throw new MessagingException(command+" failed: "+response);
}
} catch (IOException e) {
throw new MessagingException(command+" failed", e);
}
}
}
command = "XOVER "+newsgroup.first+"-"+newsgroup.last;
Vector av = new Vector(Math.max(newsgroup.last-newsgroup.first, 10));
synchronized (this) {
try {
send(command);
switch (getResponse()) {
case LISTING_OVERVIEW:
String line;
for (line=in.readLine(); line!=null && !".".equals(line); line = in.readLine()) {
int tabIndex = line.indexOf('\t');
if (tabIndex>-1) {
int msgnum = Integer.parseInt(line.substring(0, tabIndex));
Article article = new Article(newsgroup, msgnum);
article.addXoverHeaders(getOverviewHeaders(format, line, tabIndex));
av.addElement(article);
} else
throw new ProtocolException("Invalid overview line format");
}
break;
case NO_ARTICLE_SELECTED:
case PERMISSION_DENIED:
break;
case NO_GROUP_SELECTED:
case SERVER_ERROR:
default:
throw new MessagingException(command+" failed: "+response);
}
} catch (IOException e) {
throw new MessagingException(command+" failed", e);
} catch (NumberFormatException e) {
throw new MessagingException(command+" failed", e);
}
}
Article[] articles = new Article[av.size()]; av.copyInto(articles);
return articles;
}
// Returns an InternetHeaders object representing the headers stored in an xover response line.
InternetHeaders getOverviewHeaders(String[] format, String line, int startIndex) {
InternetHeaders headers = new InternetHeaders();
for (int i=0; i<format.length; i++) {
//System.out.println("Processing fmt key: "+format[i]);
int colonIndex = format[i].indexOf(':');
String key = format[i].substring(0, colonIndex);
boolean full = "full".equals(format[i].substring(colonIndex+1, format[i].length()));
int tabIndex = line.indexOf('\t', startIndex+1);
if (tabIndex<0) tabIndex = line.length();
String value = line.substring(startIndex+1, tabIndex);
if (full)
value = value.substring(value.indexOf(':')+1).trim();
headers.addHeader(key, value);
//System.out.println("Added header: "+key+", "+value);
startIndex = tabIndex;
}
return headers;
}
boolean validateOverviewHeader(String key) throws MessagingException {
String[] format = getOverviewFormat();
for (int i=0; i<format.length; i++) {
if (key.equalsIgnoreCase(format[i].substring(0, format[i].indexOf(':'))))
return true;
}
return false;
}
public void addStatusListener(StatusListener l) {
synchronized (statusListeners) {
statusListeners.addElement(l);
}
}
public void removeStatusListener(StatusListener l) {
synchronized (statusListeners) {
statusListeners.removeElement(l);
}
}
protected void processStatusEvent(StatusEvent event) {
StatusListener[] listeners;
synchronized (statusListeners) {
listeners = new StatusListener[statusListeners.size()];
statusListeners.copyInto(listeners);
}
switch (event.getType()) {
case event.OPERATION_START:
for (int i=0; i<listeners.length; i++)
listeners[i].statusOperationStarted(event);
break;
case event.OPERATION_UPDATE:
for (int i=0; i<listeners.length; i++)
listeners[i].statusProgressUpdate(event);
break;
case event.OPERATION_END:
for (int i=0; i<listeners.length; i++)
listeners[i].statusOperationEnded(event);
break;
}
}
/**
* The root holds the newsgroups in an NNTPStore.
*/
class Root extends Folder {
/**
* Constructor.
*/
protected Root(Store store) { super(store); }
/**
* Returns the name of this folder.
*/
public String getName() { return "/"; }
/**
* Returns the full name of this folder.
*/
public String getFullName() { return getName(); }
/**
* Returns the type of this folder.
*/
public int getType() throws MessagingException { return HOLDS_FOLDERS; }
/**
* Indicates whether this folder exists.
*/
public boolean exists() throws MessagingException { return true; }
/**
* Indicates whether this folder contains any new articles.
*/
public boolean hasNewMessages() throws MessagingException { return false; }
/**
* Opens this folder.
*/
public void open(int mode) throws MessagingException {
if (mode!=this.READ_ONLY) throw new MessagingException("Folder is read-only");
}
/**
* Closes this folder.
*/
public void close(boolean expunge) throws MessagingException {}
/**
* Expunges this folder.
*/
public Message[] expunge() throws MessagingException { return new Message[0]; }
/**
* Indicates whether this folder is open.
*/
public boolean isOpen() { return true; }
/**
* Returns the permanent flags for this folder.
*/
public Flags getPermanentFlags() { return new Flags(); }
/**
* Returns the number of articles in this folder.
*/
public int getMessageCount() throws MessagingException { return 0; }
/**
* Returns the articles in this folder.
*/
public Message[] getMessages() throws MessagingException {
throw new MessagingException("Folder can't contain messages");
}
/**
* Returns the specified message in this folder.
* Since NNTP articles are not stored in sequential order,
* the effect is just to reference articles returned by getMessages().
*/
public Message getMessage(int msgnum) throws MessagingException {
throw new MessagingException("Folder can't contain messages");
}
/**
* Root folder is read-only.
*/
public void appendMessages(Message aarticle[]) throws MessagingException {
throw new MessagingException("Folder is read-only");
}
/**
* Does nothing.
*/
public void fetch(Message articles[], FetchProfile fetchprofile) throws MessagingException {
}
/**
* This folder does not have a parent.
*/
public Folder getParent() throws MessagingException { return null; }
/**
* Returns the newsgroups on the server.
*/
public Folder[] list() throws MessagingException {
return getNewsgroups("LIST ACTIVE", false);
}
/**
* Returns the newsgroups on the server.
*/
public Folder[] list(String pattern) throws MessagingException {
return getNewsgroups(pattern, false);
}
/**
* Returns the subscribed newsgroups on the server.
*/
public Folder[] listSubscribed() throws MessagingException {
Vector groups = new Vector();
for (Enumeration enum = newsgroups.elements(); enum.hasMoreElements(); ) {
Newsgroup group = (Newsgroup)enum.nextElement();
if (group.subscribed)
groups.addElement(group);
}
Folder[] list = new Folder[groups.size()]; groups.copyInto(list);
return list;
}
/**
* Returns the subscribed newsgroups on the server.
*/
public Folder[] listSubscribed(String pattern) throws MessagingException {
return listSubscribed();
}
/**
* Returns the newsgroup with the specified name.
*/
public Folder getFolder(String name) throws MessagingException {
return getNewsgroup(name);
}
/**
* Returns the separator character.
*/
public char getSeparator() throws MessagingException {
return '.';
}
/**
* Root folders cannot be created, deleted, or renamed.
*/
public boolean create(int i) throws MessagingException {
throw new MessagingException("Folder cannot be created");
}
/**
* Root folders cannot be created, deleted, or renamed.
*/
public boolean delete(boolean flag) throws MessagingException {
throw new MessagingException("Folder cannot be deleted");
}
/**
* Root folders cannot be created, deleted, or renamed.
*/
public boolean renameTo(Folder folder) throws MessagingException {
throw new MessagingException("Folder cannot be renamed");
}
}
class NNTPTransport extends Transport {
NNTPTransport(Session session, URLName urlname) {
super(session, urlname);
}
public boolean protocolConnect(String host, int port, String user, String password) throws MessagingException {
return true;
}
public void sendMessage(Message message, Address[] addresses) throws MessagingException {
postArticle(message, addresses);
}
}
}