/* * $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(); } } }