Source for gnu.inet.nntp.FileNewsrc

   1: /*
   2:  * FileNewsrc.java
   3:  * Copyright (C) 2002 The Free Software Foundation
   4:  * 
   5:  * This file is part of GNU inetlib, a library.
   6:  * 
   7:  * GNU inetlib is free software; you can redistribute it and/or modify
   8:  * it under the terms of the GNU General Public License as published by
   9:  * the Free Software Foundation; either version 2 of the License, or
  10:  * (at your option) any later version.
  11:  * 
  12:  * GNU inetlib is distributed in the hope that it will be useful,
  13:  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14:  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15:  * GNU General Public License for more details.
  16:  * 
  17:  * You should have received a copy of the GNU General Public License
  18:  * along with this library; if not, write to the Free Software
  19:  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  20:  *
  21:  * Linking this library statically or dynamically with other modules is
  22:  * making a combined work based on this library.  Thus, the terms and
  23:  * conditions of the GNU General Public License cover the whole
  24:  * combination.
  25:  *
  26:  * As a special exception, the copyright holders of this library give you
  27:  * permission to link this library with independent modules to produce an
  28:  * executable, regardless of the license terms of these independent
  29:  * modules, and to copy and distribute the resulting executable under
  30:  * terms of your choice, provided that you also meet, for each linked
  31:  * independent module, the terms and conditions of the license of that
  32:  * module.  An independent module is a module which is not derived from
  33:  * or based on this library.  If you modify this library, you may extend
  34:  * this exception to your version of the library, but you are not
  35:  * obliged to do so.  If you do not wish to do so, delete this
  36:  * exception statement from your version.
  37:  */
  38: 
  39: package gnu.inet.nntp;
  40: 
  41: import java.io.BufferedReader;
  42: import java.io.BufferedOutputStream;
  43: import java.io.FileInputStream;
  44: import java.io.FileOutputStream;
  45: import java.io.InputStreamReader;
  46: import java.io.File;
  47: import java.io.FileNotFoundException;
  48: import java.io.IOException;
  49: import java.util.ArrayList;
  50: import java.util.HashMap;
  51: import java.util.Iterator;
  52: import java.util.LinkedList;
  53: import java.util.List;
  54: import java.util.Map;
  55: 
  56: /**
  57:  * A .newsrc configuration on a filesystem.
  58:  *
  59:  * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
  60:  */
  61: public class FileNewsrc
  62:   implements Newsrc
  63: {
  64: 
  65:   private static final String NEWSRC_ENCODING = "US-ASCII";
  66: 
  67:   protected File file;
  68: 
  69:   protected List subs = null;
  70:   protected List groups = null;
  71:   protected Map lines = null;
  72:   protected boolean dirty;
  73:   protected boolean debug;
  74: 
  75:   /**
  76:    * Constructor.
  77:    * @param file the disk file
  78:    * @param debug for debugging information on stderr
  79:    */
  80:   public FileNewsrc(File file, boolean debug)
  81:   {
  82:     this.file = file;
  83:     this.debug = debug;
  84:   }
  85: 
  86:   public void close()
  87:   {
  88:     if (!dirty)
  89:       {
  90:         return;
  91:       }
  92:     save();
  93:   }
  94:   
  95:   /**
  96:    * Load the file.
  97:    */
  98:   void load()
  99:   {
 100:     long fs = file.length();
 101:     long max = (long) Integer.MAX_VALUE;
 102:     int bs = (int) (fs > max ? max : fs);
 103:     
 104:     groups = new LinkedList();
 105:     lines = new HashMap(bs / 20);
 106:     subs = new LinkedList();
 107:     
 108:     // Load
 109:     try
 110:       {
 111:         long t1 = System.currentTimeMillis();
 112:         if (debug)
 113:           {
 114:             System.err.println("DEBUG: nntp: newsrc loading " +
 115:                                file.getPath());
 116:           }
 117:         
 118:         FileInputStream fr = new FileInputStream(file);
 119:         InputStreamReader ir = new InputStreamReader(fr, NEWSRC_ENCODING);
 120:         BufferedReader reader = new BufferedReader(ir, bs);
 121:         String line = reader.readLine();
 122:         while (line != null)
 123:           {
 124:             int cp = line.indexOf(':');
 125:             if (cp > -1)
 126:               {
 127:                 // Subscribed newsgroup
 128:                 String name = line.substring(0, cp);
 129:                 groups.add(name);
 130:                 subs.add(name);
 131:                 cp++;
 132:                 if (cp < line.length())
 133:                   {
 134:                     String tail = line.substring(cp).trim();
 135:                     if (tail.length() > 0)
 136:                       {
 137:                         lines.put(name, tail);
 138:                       }
 139:                   }
 140:               }
 141:             else
 142:               {
 143:                 int pp = line.indexOf('!');
 144:                 if (pp > -1)
 145:                   {
 146:                     // Unsubscribed newsgroup
 147:                     String name = line.substring(0, pp);
 148:                     groups.add(name);
 149:                     pp++;
 150:                     if (pp < line.length())
 151:                       {
 152:                         String tail = line.substring(pp).trim();
 153:                         if (tail.length() > 0)
 154:                           {
 155:                             lines.put(name, tail);
 156:                           }
 157:                       }
 158:                   }
 159:                 // else ignore - comments etc will not be saved!
 160:               }
 161:             line = reader.readLine();
 162:           }
 163:         reader.close();
 164:         long t2 = System.currentTimeMillis();
 165:         if (debug)
 166:           {
 167:             System.err.println("DEBUG: nntp: newsrc load: " +
 168:                                groups.size() + " groups in " +
 169:                                (t2 - t1) + "ms");
 170:           }
 171:       }
 172:     catch (FileNotFoundException e)
 173:       {
 174:       }
 175:     catch (IOException e)
 176:       {
 177:         System.err.println("WARNING: nntp: unable to read newsrc file");
 178:         if (debug)
 179:           {
 180:             e.printStackTrace(System.err);
 181:           }
 182:       }
 183:     catch (SecurityException e)
 184:       {
 185:         System.err.println("WARNING: nntp: " +
 186:                            "no read permission on newsrc file");
 187:       }
 188:     dirty = false;
 189:   }
 190:   
 191:   /**
 192:    * Save the file.
 193:    */
 194:   void save()
 195:   {
 196:     try
 197:       {
 198:         long t1 = System.currentTimeMillis();
 199:         if (debug)
 200:           {
 201:             System.err.println("DEBUG: nntp: newsrc saving " +
 202:                                file.getPath());
 203:           }
 204: 
 205:         int bs = (groups.size() * 20);    // guess an average line length
 206:         FileOutputStream fw = new FileOutputStream(file);
 207:         BufferedOutputStream writer = new BufferedOutputStream(fw, bs);
 208:         for (Iterator i = groups.iterator(); i.hasNext();)
 209:           {
 210:             String group = (String) i.next();
 211:             StringBuffer buffer = new StringBuffer(group);
 212:             if (subs.contains(group))
 213:               {
 214:                 buffer.append(':');
 215:               }
 216:             else
 217:               {
 218:                 buffer.append('!');
 219:               }
 220:             Object r = lines.get(group);
 221:             if (r instanceof String)
 222:               {
 223:                 buffer.append((String) r);
 224:               }
 225:             else
 226:               {
 227:                 RangeList ranges = (RangeList) r;
 228:                 if (ranges != null)
 229:                   {
 230:                     buffer.append(ranges.toString());
 231:                   }
 232:               }
 233:             buffer.append('\n');
 234: 
 235:             byte[] bytes = buffer.toString().getBytes(NEWSRC_ENCODING);
 236:             writer.write(bytes);
 237:           }
 238:         writer.flush();
 239:         writer.close();
 240: 
 241:         long t2 = System.currentTimeMillis();
 242:         if (debug)
 243:           {
 244:             System.err.println("DEBUG: nntp: newsrc save: " +
 245:                                groups.size() + " groups in " +
 246:                                (t2 - t1) + "ms");
 247:           }
 248:       }
 249:     catch (IOException e)
 250:       {
 251:         System.err.println("WARNING: nntp: unable to save newsrc file");
 252:         if (debug)
 253:           {
 254:             e.printStackTrace(System.err);
 255:           }
 256:       }
 257:     dirty = false;
 258:   }
 259: 
 260:   /**
 261:    * Returns an iterator over the names of the currently subscribed
 262:    * newsgroups.
 263:    */
 264:   public Iterator list()
 265:   {
 266:     if (subs == null)
 267:       {
 268:         load();
 269:       }
 270:     return subs.iterator();
 271:   }
 272: 
 273:   public boolean isSubscribed(String newsgroup)
 274:   {
 275:     if (subs == null)
 276:       {
 277:         load();
 278:       }
 279:     return (subs.contains(newsgroup));
 280:   }
 281: 
 282:   public void setSubscribed(String newsgroup, boolean flag)
 283:   {
 284:     if (subs == null)
 285:       {
 286:         load();
 287:       }
 288:     if (flag && !groups.contains(newsgroup))
 289:       {
 290:         groups.add(newsgroup);
 291:       }
 292:     boolean subscribed = subs.contains(newsgroup);
 293:     if (flag && !subscribed)
 294:       {
 295:         subs.add(newsgroup);
 296:         dirty = true;
 297:       }
 298:     else if (!flag && subscribed)
 299:       {
 300:         subs.remove(newsgroup);
 301:         dirty = true;
 302:       }
 303:   }
 304: 
 305:   public boolean isSeen(String newsgroup, int article)
 306:   {
 307:     if (subs == null)
 308:       {
 309:         load();
 310:       }
 311:     Object value = lines.get(newsgroup);
 312:     if (value instanceof String)
 313:       {
 314:         value = new RangeList((String) value);
 315:       }
 316:     RangeList ranges = (RangeList) value;
 317:     if (ranges != null)
 318:       {
 319:         return ranges.isSeen(article);
 320:       }
 321:     return false;
 322:   }
 323: 
 324:   public void setSeen(String newsgroup, int article, boolean flag)
 325:   {
 326:     if (subs == null)
 327:       {
 328:         load();
 329:       }
 330:     Object value = lines.get(newsgroup);
 331:     if (value instanceof String)
 332:       {
 333:         value = new RangeList((String) value);
 334:       }
 335:     RangeList ranges = (RangeList) value;
 336:     if (ranges == null)
 337:       {
 338:         ranges = new RangeList();
 339:         lines.put(newsgroup, ranges);
 340:         dirty = true;
 341:       }
 342:     if (ranges.isSeen(article) != flag)
 343:       {
 344:         ranges.setSeen(article, flag);
 345:         dirty = true;
 346:       }
 347:   }
 348: 
 349:   /**
 350:    * A RangeList holds a series of ranges that are ordered and
 351:    * non-overlapping.
 352:    */
 353:   static class RangeList
 354:   {
 355: 
 356:     List seen;
 357: 
 358:     RangeList()
 359:     {
 360:       seen = new ArrayList();
 361:     }
 362: 
 363:     RangeList(String line)
 364:     {
 365:       this();
 366:       try
 367:         {
 368:           // Parse the line at comma delimiters.
 369:           int start = 0;
 370:           int end = line.indexOf(',');
 371:           while (end > start)
 372:             {
 373:               String token = line.substring(start, end);
 374:               addToken(token);
 375:               start = end + 1;
 376:               end = line.indexOf(',', start);
 377:             }
 378:           addToken(line.substring(start));
 379:         }
 380:       catch (NumberFormatException e)
 381:         {
 382:           System.err.println("ERROR: nntp: bad newsrc format: " + line);
 383:         }
 384:     }
 385: 
 386:     /*
 387:      * Used during initial parse.
 388:      */
 389:     private void addToken(String token) throws NumberFormatException
 390:     {
 391:       int hp = token.indexOf('-');
 392:       if (hp > -1)
 393:         {
 394:           // Range
 395:           String fs = token.substring(0, hp);
 396:           String ts = token.substring(hp + 1);
 397:           int from = Integer.parseInt(fs);
 398:           int to = Integer.parseInt(ts);
 399:           if (from > -1 && to > -1)
 400:             {
 401:               insert(from, to);
 402:             }
 403:         }
 404:       else
 405:         {
 406:           // Single number
 407:           int number = Integer.parseInt(token);
 408:           if (number > -1)
 409:             {
 410:               insert(number);
 411:             }
 412:         }
 413:     }
 414: 
 415:     /**
 416:      * Indicates whether the specified article is seen.
 417:      */
 418:     public boolean isSeen(int num)
 419:     {
 420:       int len = seen.size();
 421:       Range[] r = new Range[len];
 422:       seen.toArray(r);
 423:       for (int i = 0; i < len; i++)
 424:         {
 425:           if (r[i].contains(num))
 426:             {
 427:               return true;
 428:             }
 429:         }
 430:       return false;
 431:     }
 432: 
 433:     /**
 434:      * Sets whether the specified article is seen.
 435:      */
 436:     public void setSeen(int num, boolean flag)
 437:     {
 438:       if (flag)
 439:         {
 440:           insert(num);
 441:         }
 442:       else
 443:         {
 444:           remove(num);
 445:         }
 446:     }
 447: 
 448:     /*
 449:      * Find the index within seen to insert the specified article.
 450:      * The range object at the returned index may already contain num.
 451:      */
 452:     int indexOf(int num)
 453:     {
 454:       int len = seen.size();
 455:       Range[] r = new Range[len];
 456:       seen.toArray(r);
 457:       for (int i = 0; i < len; i++)
 458:         {
 459:           if (r[i].contains(num))
 460:             {
 461:               return i;
 462:             }
 463:           if (r[i].from > num)
 464:             {
 465:               return i;
 466:             }
 467:           if (r[i].to == num - 1)
 468:             {
 469:               return i;
 470:             }
 471:         }
 472:       return len;
 473:     }
 474: 
 475:     void insert(int start, int end)
 476:     {
 477:       Range range = new Range(start, end);
 478:       int i1 = indexOf(range.from);
 479:       // range is at end
 480:       if (i1 == seen.size())
 481:         {
 482:           seen.add(range);
 483:           return;
 484:         }
 485:       Range r1 = (Range) seen.get(i1);
 486:       // range is before r1
 487:       if (range.to < r1.from)
 488:         {
 489:           seen.add(i1, range);
 490:           return;
 491:         }
 492:       // range is a subset of r1
 493:       if (r1.from <= range.from && r1.to >= range.to)
 494:         {
 495:           return;
 496:         }
 497:       // range is a superset of r1
 498:       int i2 = indexOf(range.to);
 499:       Range r2 = (Range) seen.get(i2);
 500:       System.err.println("r2 " + r2 + " i2 " + i2);
 501:       // remove all ranges between
 502:       for (int i = i2; i >= i1; i--)
 503:         {
 504:           seen.remove(i);
 505:         }
 506:       // merge
 507:       int f = (range.from < r1.from) ? range.from : r1.from;
 508:       int t = (range.to > r2.to) ? range.to : r2.to;
 509:       range = new Range(f, t);
 510:       seen.add(i1, range);
 511:     }
 512: 
 513:     void insert(int num)
 514:     {
 515:       insert(num, num);
 516:     }
 517: 
 518:     void remove(int num)
 519:     {
 520:       int i = indexOf(num);
 521:       Range r = (Range) seen.get(i);
 522:       seen.remove(i);
 523:       // num == r
 524:       if ((r.from == r.to) &&(r.to == num))
 525:         {
 526:           return;
 527:         }
 528:       // split r
 529:       if (r.to > num)
 530:         {
 531:           Range r2 = new Range(num + 1, r.to);
 532:           seen.add(i, r2);
 533:         }
 534:       if (r.from < num)
 535:         {
 536:           Range r2 = new Range(r.from, num - 1);
 537:           seen.add(i, r2);
 538:         }
 539:     }
 540: 
 541:     public String toString()
 542:     {
 543:       StringBuffer buf = new StringBuffer();
 544:       int len = seen.size();
 545:       for (int i = 0; i < len; i++)
 546:         {
 547:           Range range = (Range) seen.get(i);
 548:           if (i > 0)
 549:             {
 550:               buf.append(',');
 551:             }
 552:           buf.append(range.toString());
 553:         }
 554:       return buf.toString();
 555:     }
 556: 
 557:   }
 558: 
 559:   /**
 560:    * A range is either a single integer or a range between two integers.
 561:    */
 562:   static class Range
 563:   {
 564:     int from;
 565:     int to;
 566: 
 567:     public Range(int i)
 568:     {
 569:       from = to = i;
 570:     }
 571: 
 572:     public Range(int f, int t)
 573:     {
 574:       if (f > t)
 575:         {
 576:           from = t;
 577:           to = f;
 578:         }
 579:       else
 580:         {
 581:           from = f;
 582:           to = t;
 583:         }
 584:     }
 585: 
 586:     public boolean contains(int num)
 587:     {
 588:       return (num >= from && num <= to);
 589:     }
 590: 
 591:     public String toString()
 592:     {
 593:       if (from != to)
 594:         {
 595:           return new StringBuffer()
 596:             .append(from)
 597:             .append('-')
 598:             .append(to)
 599:             .toString();
 600:         }
 601:       else
 602:         {
 603:           return Integer.toString(from);
 604:         }
 605:     }
 606: 
 607:   }
 608: 
 609: }