/*
* $Id: FileNewsrc.java,v 1.1 2003/10/19 08:51:37 dog Exp $
* Copyright (C) 2002 The Free Software Foundation
*
* This file is part of GNU inetlib, a library.
*
* GNU inetlib 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 2 of the License, or
* (at your option) any later version.
*
* GNU inetlib 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 library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* As a special exception, if you link this library with other files to
* produce an executable, this library does not by itself cause the
* resulting executable to be covered by the GNU General Public License.
* This exception does not however invalidate any other reasons why the
* executable file might be covered by the GNU General Public License.
*/
package gnu.inet.nntp;
import java.io.BufferedReader;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* A .newsrc configuration on a filesystem.
*
* @author Chris Burdess
* @version $Revision: 1.1 $ $Date: 2003/10/19 08:51:37 $
*/
public class FileNewsrc
implements Newsrc
{
private static final String NEWSRC_ENCODING = "US-ASCII";
protected File file;
protected List subs = null;
protected List groups = null;
protected Map lines = null;
protected boolean dirty;
protected boolean debug;
/**
* Constructs a FileNewsrc for the specified url.
*/
public FileNewsrc(File file, boolean debug)
{
this.file = file;
this.debug = debug;
}
public void close()
{
if (!dirty)
return;
save();
}
/**
* Load the file.
*/
void load()
{
long fs = file.length();
long max = (long)Integer.MAX_VALUE;
int bs = (int)(fs>max ? max : fs);
groups = new LinkedList();
lines = new HashMap(bs/20);
subs = new LinkedList();
// Load
try
{
long t1 = System.currentTimeMillis();
//long rcount = 0;
if (debug)
System.err.println("DEBUG: nntp: newsrc loading "+file.getPath());
FileInputStream fr = new FileInputStream(file);
InputStreamReader ir = new InputStreamReader(fr, NEWSRC_ENCODING);
BufferedReader reader = new BufferedReader(ir, bs);
//long tt1 = System.currentTimeMillis();
String line = reader.readLine();
//long tt2 = System.currentTimeMillis();
//rcount += (tt2-tt1);
while (line!=null)
{
int cp = line.indexOf(':');
if (cp>-1)
{
// Subscribed newsgroup
String name = line.substring(0, cp);
groups.add(name);
subs.add(name);
cp++;
if (cp0)
lines.put(name, tail);
}
}
else
{
int pp = line.indexOf('!');
if (pp>-1)
{
// Unsubscribed newsgroup
String name = line.substring(0, pp);
groups.add(name);
pp++;
if (pp0)
lines.put(name, tail);
}
}
// else ignore - comments etc will not be saved!
}
//tt1 = System.currentTimeMillis();
line = reader.readLine();
//tt2 = System.currentTimeMillis();
//rcount += (tt2-tt1);
}
reader.close();
long t2 = System.currentTimeMillis();
//System.err.println("rcount="+rcount);
//System.err.println("total="+(t2-t1));
if (debug)
System.err.println("DEBUG: nntp: newsrc load: "+
groups.size()+" groups in "+(t2-t1)+"ms");
}
catch (FileNotFoundException e)
{
}
catch (IOException e)
{
System.err.println("WARNING: nntp: unable to read newsrc file");
if (debug)
e.printStackTrace(System.err);
}
catch (SecurityException e)
{
System.err.println("WARNING: nntp: no read permission on newsrc file");
}
dirty = false;
}
/**
* Save the file.
*/
void save()
{
try
{
long t1 = System.currentTimeMillis();
if (debug)
System.err.println("DEBUG: nntp: newsrc saving "+file.getPath());
int bs = (groups.size()*20); // guess an average line length
FileOutputStream fw = new FileOutputStream(file);
BufferedOutputStream writer = new BufferedOutputStream(fw, bs);
for (Iterator i = groups.iterator(); i.hasNext(); )
{
String group = (String)i.next();
StringBuffer buffer = new StringBuffer(group);
if (subs.contains(group))
buffer.append(':');
else
buffer.append('!');
Object r = lines.get(group);
if (r instanceof String)
buffer.append((String)r);
else
{
RangeList ranges = (RangeList)r;
if (ranges!=null)
buffer.append(ranges.toString());
}
buffer.append('\n');
byte[] bytes = buffer.toString().getBytes(NEWSRC_ENCODING);
writer.write(bytes);
}
writer.flush();
writer.close();
long t2 = System.currentTimeMillis();
if (debug)
System.err.println("DEBUG: nntp: newsrc save: "+
groups.size()+" groups in "+(t2-t1)+"ms");
}
catch (IOException e)
{
System.err.println("WARNING: nntp: unable to save newsrc file");
if (debug)
e.printStackTrace(System.err);
}
dirty = false;
}
/**
* Returns an iterator over the names of the currently subscribed
* newsgroups.
*/
public Iterator list()
{
if (subs==null)
load();
return subs.iterator();
}
public boolean isSubscribed(String newsgroup)
{
if (subs==null)
load();
return (subs.contains(newsgroup));
}
public void setSubscribed(String newsgroup, boolean flag)
{
if (subs==null)
load();
boolean subscribed = subs.contains(newsgroup);
if (flag && !subscribed)
{
subs.add(newsgroup);
dirty = true;
}
else if (!flag && subscribed)
{
subs.remove(newsgroup);
dirty = true;
}
}
public boolean isSeen(String newsgroup, int article)
{
if (subs==null)
load();
Object value = lines.get(newsgroup);
if (value instanceof String)
value = new RangeList((String)value);
RangeList ranges = (RangeList)value;
if (ranges!=null)
return ranges.isSeen(article);
return false;
}
public void setSeen(String newsgroup, int article, boolean flag)
{
if (subs==null)
load();
Object value = lines.get(newsgroup);
if (value instanceof String)
value = new RangeList((String)value);
RangeList ranges = (RangeList)value;
if (ranges==null)
{
ranges = new RangeList();
lines.put(newsgroup, ranges);
}
if (ranges.isSeen(article)!=flag)
{
ranges.setSeen(article, flag);
dirty = true;
}
}
/**
* A RangeList holds a series of ranges that are ordered and
* non-overlapping.
*/
static class RangeList
{
List seen;
RangeList()
{
seen = new LinkedList();
}
RangeList(String line)
{
this();
try
{
// Parse the line at comma delimiters.
int start = 0;
int end = line.indexOf(',');
while (end>start)
{
String token = line.substring(start, end);
addToken(token);
start = end+1;
end = line.indexOf(',', start);
}
addToken(line.substring(start));
}
catch (NumberFormatException e)
{
System.err.println("ERROR: nntp: bad newsrc format: "+line);
}
}
/*
* Used during initial parse.
*/
private void addToken(String token)
throws NumberFormatException
{
int hp = token.indexOf('-');
if (hp>-1)
{
// Range
String fs = token.substring(0, hp);
String ts = token.substring(hp+1);
int from = Integer.parseInt(fs);
int to = Integer.parseInt(ts);
if (from>-1 && to>-1)
insert(from, to);
}
else
{
// Single number
int number = Integer.parseInt(token);
if (number>-1)
insert(number);
}
}
/**
* Indicates whether the specified article is seen.
*/
public boolean isSeen(int num)
{
int len = seen.size();
Range[] r = new Range[len];
seen.toArray(r);
for (int i=0; inum)
return i;
if (r[i].to==num-1)
return i;
}
return len;
}
void insert(int start, int end)
{
Range range = new Range(start, end);
int i1 = indexOf(range.from);
// range is at end
if (i1==seen.size())
{
seen.add(range);
return;
}
Range r1 = (Range)seen.get(i1);
// range is before r1
if (range.to=range.to)
return;
// range is a superset of r1
int i2 = indexOf(range.to);
Range r2 = (Range)seen.get(i2);
System.err.println("r2 "+r2+" i2 "+i2);
// remove all ranges between
for (int i=i2; i>=i1; i--)
seen.remove(i);
// merge
int f = (range.fromr2.to) ? range.to : r2.to;
range = new Range(f, t);
seen.add(i1, range);
}
void insert(int num)
{
insert(num, num);
}
void remove(int num)
{
int i = indexOf(num);
Range r = (Range)seen.get(i);
seen.remove(i);
// num == r
if ((r.from==r.to) && (r.to==num))
return;
// split r
if (r.to>num)
{
Range r2 = new Range(num+1, r.to);
seen.add(i, r2);
}
if (r.from0)
buf.append(',');
buf.append(range.toString());
}
return buf.toString();
}
}
/**
* A range is either a single integer or a range between two integers.
*/
static class Range
{
int from;
int to;
public Range(int i)
{
from = to = i;
}
public Range(int f, int t)
{
if (f>t)
{
from = t;
to = f;
}
else
{
from = f;
to = t;
}
}
public boolean contains(int num)
{
return (num>=from && num<=to);
}
public String toString()
{
StringBuffer buf = new StringBuffer();
buf.append(from);
if (from != to)
buf.append("-").append(to);
return buf.toString();
}
}
}