[tor-commits] [metrics-lib/master] Use a single interface for reading descriptors.

karsten at torproject.org karsten at torproject.org
Thu Feb 2 07:36:35 UTC 2012


commit b2ad143c2a79e335f0ff7648b1dcd5fc157af63c
Author: Karsten Loesing <karsten.loesing at gmx.net>
Date:   Thu Feb 2 08:08:07 2012 +0100

    Use a single interface for reading descriptors.
---
 .../descriptor/BridgeDescriptorReader.java         |   34 ---
 .../descriptor/BridgePoolAssignmentReader.java     |   34 ---
 .../descriptor/DescriptorDownloader.java           |  109 ++++++++
 .../torproject/descriptor/DescriptorReader.java    |   34 +++
 .../descriptor/DescriptorSourceFactory.java        |   28 +--
 .../descriptor/RelayDescriptorDownloader.java      |  109 --------
 .../descriptor/RelayDescriptorReader.java          |   35 ---
 .../descriptor/impl/DescriptorDownloaderImpl.java  |  265 ++++++++++++++++++++
 .../descriptor/impl/DescriptorReaderImpl.java      |  194 ++++++++++++++
 .../impl/RelayDescriptorDownloaderImpl.java        |  265 --------------------
 .../impl/RelayOrBridgeDescriptorReaderImpl.java    |  198 ---------------
 11 files changed, 610 insertions(+), 695 deletions(-)

diff --git a/src/org/torproject/descriptor/BridgeDescriptorReader.java b/src/org/torproject/descriptor/BridgeDescriptorReader.java
deleted file mode 100644
index d827124..0000000
--- a/src/org/torproject/descriptor/BridgeDescriptorReader.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/* Copyright 2011, 2012 The Tor Project
- * See LICENSE for licensing information */
-package org.torproject.descriptor;
-
-import java.io.File;
-import java.util.Iterator;
-
-/* Read bridge descriptors from one or more local directories. */
-public interface BridgeDescriptorReader {
-
-  /* Add a local directory to read bridge descriptors from. */
-  public void addDirectory(File directory);
-
-  /* Exclude files that are contained in the given history file and that
-   * haven't changed since they were last read.  Add reads from the
-   * current run to the history file.  Remove files that don't exist
-   * anymore from the history file.  Lines in the history file contain the
-   * last modified timestamp and the absolute path of a file. */
-  public void setExcludeFiles(File historyFile);
-
-  /* Fail descriptor parsing when encountering an unrecognized line.  This
-   * is not set by default, because the Tor specifications allow for new
-   * lines to be added that shall be ignored by older Tor versions.  But
-   * some applications may want to handle unrecognized descriptor lines
-   * explicitly. */
-  public void setFailUnrecognizedDescriptorLines();
-
-  /* Read the previously configured bridge descriptors and make them
-   * available via the returned blocking iterator.  Whenever the reader
-   * runs out of descriptors and expects to provide more shortly after, it
-   * blocks the caller.  This method can only be run once. */
-  public Iterator<DescriptorFile> readDescriptors();
-}
-
diff --git a/src/org/torproject/descriptor/BridgePoolAssignmentReader.java b/src/org/torproject/descriptor/BridgePoolAssignmentReader.java
deleted file mode 100644
index 13c8ae3..0000000
--- a/src/org/torproject/descriptor/BridgePoolAssignmentReader.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/* Copyright 2011, 2012 The Tor Project
- * See LICENSE for licensing information */
-package org.torproject.descriptor;
-
-import java.io.File;
-import java.util.Iterator;
-
-/* Read bridge pool assignments from one or more local directories. */
-public interface BridgePoolAssignmentReader {
-
-  /* Add a local directory to read bridge pool assignments from. */
-  public void addDirectory(File directory);
-
-  /* Exclude files that are contained in the given history file and that
-   * haven't changed since they were last read.  Add reads from the
-   * current run to the history file.  Remove files that don't exist
-   * anymore from the history file.  Lines in the history file contain the
-   * last modified timestamp and the absolute path of a file. */
-  public void setExcludeFiles(File historyFile);
-
-  /* Fail descriptor parsing when encountering an unrecognized line.  This
-   * is not set by default, because the Tor specifications allow for new
-   * lines to be added that shall be ignored by older Tor versions.  But
-   * some applications may want to handle unrecognized descriptor lines
-   * explicitly. */
-  public void setFailUnrecognizedDescriptorLines();
-
-  /* Read the previously configured bridge pool assignments and make them
-   * available via the returned blocking iterator.  Whenever the reader
-   * runs out of descriptors and expects to provide more shortly after, it
-   * blocks the caller.  This method can only be run once. */
-  public Iterator<DescriptorFile> readDescriptors();
-}
-
diff --git a/src/org/torproject/descriptor/DescriptorDownloader.java b/src/org/torproject/descriptor/DescriptorDownloader.java
new file mode 100644
index 0000000..5665e1f
--- /dev/null
+++ b/src/org/torproject/descriptor/DescriptorDownloader.java
@@ -0,0 +1,109 @@
+/* Copyright 2011, 2012 The Tor Project
+ * See LICENSE for licensing information */
+package org.torproject.descriptor;
+
+import java.util.Iterator;
+import java.util.Set;
+
+/* Download relay descriptors from directory mirrors or authorities. */
+public interface DescriptorDownloader {
+
+  /* Add a directory authority to download descriptors from.  A directory
+   * authority is only required for downloading network status vote and
+   * will be used when no directory mirrors are available. */
+  public void addDirectoryAuthority(String nickname, String ip,
+      int dirPort);
+
+  /* Add a directory mirror to download descriptors from.  Directory
+   * mirrors are preferred when downloading descriptors, except for
+   * network status votes which are only available on directory
+   * authorities. */
+  public void addDirectoryMirror(String nickname, String ip, int dirPort);
+
+  /* Include the current network status consensus in the downloads. */
+  public void setIncludeCurrentConsensus();
+
+  /* Include the current network status consensus in the downloads, and
+   * attempt to download it from all directory authorities.  The primary
+   * purpose of doing this is to compare different consensuses and
+   * download characteristics to each other.  Typically, downloading from
+   * a single directory mirror or authority is sufficient. */
+  public void setIncludeCurrentConsensusFromAllDirectoryAuthorities();
+
+  /* Include the current network status votes referenced from a previously
+   * downloaded consensus in the downloads.  This requires downloading the
+   * current consensus from at least one directory mirror or authority. */
+  public void setIncludeCurrentReferencedVotes();
+
+  /* Include the current network status vote published by the given
+   * directory authority in the downloads.  This requires downloading from
+   * at least one directory authority. */
+  public void setIncludeCurrentVote(String fingerprint);
+
+  /* Include the current network status votes published by the given
+   * directory authorities in the downloads.  This requires downloading
+   * from at least one directory authority. */
+  public void setIncludeCurrentVotes(Set<String> fingerprints);
+
+  /* Include all server descriptors referenced from a previously
+   * downloaded network status consensus in the downloads. */
+  public void setIncludeReferencedServerDescriptors();
+
+  /* Exclude the server descriptor with the given identifier from the
+   * downloads even if it's referenced from a consensus and we're supposed
+   * to download all referenced server descriptors. */
+  public void setExcludeServerDescriptor(String identifier);
+
+  /* Exclude the server descriptors with the given identifiers from the
+   * downloads even if they are referenced from a consensus and we're
+   * supposed to download all referenced server descriptors. */
+  public void setExcludeServerDescriptors(Set<String> identifier);
+
+  /* Include all extra-info descriptors referenced from previously
+   * downloaded server descriptors in the downloads. */
+  public void setIncludeReferencedExtraInfoDescriptors();
+
+  /* Exclude the extra-info descriptor with the given identifier from the
+   * downloads even if it's referenced from a server descriptor and we're
+   * supposed to download all referenced extra-info descriptors. */
+  public void setExcludeExtraInfoDescriptor(String identifier);
+
+  /* Exclude the extra-info descriptors with the given identifiers from
+   * the downloads even if they are referenced from server descriptors
+   * and we're supposed to download all referenced extra-info
+   * descriptors. */
+  public void setExcludeExtraInfoDescriptors(Set<String> identifiers);
+
+  /* Define a connect timeout for a single request.  If a timeout expires,
+   * no further requests will be sent to the directory authority or
+   * mirror.  Setting this value to 0 disables the connect timeout.
+   * Default value is 1 minute (60 * 1000). */
+  public void setConnectTimeout(long connectTimeoutMillis);
+
+  /* Define a read timeout for a single request.  If a timeout expires,
+   * no further requests will be sent to the directory authority or
+   * mirror.  Setting this value to 0 disables the read timeout.
+   * Default value is 1 minute (60 * 1000). */
+  public void setReadTimeout(long readTimeoutMillis);
+
+  /* Define a global timeout for all requests.  Once this timeout expires,
+   * all running requests are aborted and no further requests are made.
+   * Setting this value to 0 disables the global timeout.  Default is 1
+   * hour (60 * 60 * 1000). */
+  public void setGlobalTimeout(long globalTimeoutMillis);
+
+  /* Fail descriptor parsing when encountering an unrecognized line.  This
+   * is not set by default, because the Tor specifications allow for new
+   * lines to be added that shall be ignored by older Tor versions.  But
+   * some applications may want to handle unrecognized descriptor lines
+   * explicitly. */
+  public void setFailUnrecognizedDescriptorLines();
+
+  /* Download the previously configured relay descriptors and make them
+   * available via the returned blocking iterator.  Whenever the
+   * downloader runs out of descriptors and expects to provide more
+   * shortly after, it blocks the caller.  This method can only be run
+   * once. */
+  public Iterator<DescriptorRequest> downloadDescriptors();
+}
+
diff --git a/src/org/torproject/descriptor/DescriptorReader.java b/src/org/torproject/descriptor/DescriptorReader.java
new file mode 100644
index 0000000..1d46a79
--- /dev/null
+++ b/src/org/torproject/descriptor/DescriptorReader.java
@@ -0,0 +1,34 @@
+/* Copyright 2011, 2012 The Tor Project
+ * See LICENSE for licensing information */
+package org.torproject.descriptor;
+
+import java.io.File;
+import java.util.Iterator;
+
+/* Read descriptors from one or more local directories. */
+public interface DescriptorReader {
+
+  /* Add a local directory to read descriptors from. */
+  public void addDirectory(File directory);
+
+  /* Exclude files that are contained in the given history file and that
+   * haven't changed since they were last read.  Add reads from the
+   * current run to the history file.  Remove files that don't exist
+   * anymore from the history file.  Lines in the history file contain the
+   * last modified timestamp and the absolute path of a file. */
+  public void setExcludeFiles(File historyFile);
+
+  /* Fail descriptor parsing when encountering an unrecognized line.  This
+   * is not set by default, because the Tor specifications allow for new
+   * lines to be added that shall be ignored by older Tor versions.  But
+   * some applications may want to handle unrecognized descriptor lines
+   * explicitly. */
+  public void setFailUnrecognizedDescriptorLines();
+
+  /* Read the previously configured descriptors and make them available
+   * via the returned blocking iterator.  Whenever the reader runs out of
+   * descriptors and expects to provide more shortly after, it blocks the
+   * caller.  This method can only be run once. */
+  public Iterator<DescriptorFile> readDescriptors();
+}
+
diff --git a/src/org/torproject/descriptor/DescriptorSourceFactory.java b/src/org/torproject/descriptor/DescriptorSourceFactory.java
index ed2790a..102bfce 100644
--- a/src/org/torproject/descriptor/DescriptorSourceFactory.java
+++ b/src/org/torproject/descriptor/DescriptorSourceFactory.java
@@ -2,32 +2,20 @@
  * See LICENSE for licensing information */
 package org.torproject.descriptor;
 
-import org.torproject.descriptor.impl.RelayDescriptorDownloaderImpl;
-import org.torproject.descriptor.impl.RelayOrBridgeDescriptorReaderImpl;
+import org.torproject.descriptor.impl.DescriptorDownloaderImpl;
+import org.torproject.descriptor.impl.DescriptorReaderImpl;
 
 /* Create descriptor source instances. */
 public class DescriptorSourceFactory {
 
-  /* Create a relay descriptor reader. */
-  public static RelayDescriptorReader createRelayDescriptorReader() {
-    return new RelayOrBridgeDescriptorReaderImpl();
+  /* Create a descriptor reader. */
+  public static DescriptorReader createDescriptorReader() {
+    return new DescriptorReaderImpl();
   }
 
-  /* Create a relay descriptor downloader. */
-  public static RelayDescriptorDownloader
-      createRelayDescriptorDownloader() {
-    return new RelayDescriptorDownloaderImpl();
-  }
-
-  /* Create a bridge descriptor reader. */
-  public static BridgeDescriptorReader createBridgeDescriptorReader() {
-    return new RelayOrBridgeDescriptorReaderImpl();
-  }
-
-  /* Create a bridge pool assignment reader. */
-  public static BridgePoolAssignmentReader
-      createBridgePoolAssignmentReader() {
-    return new RelayOrBridgeDescriptorReaderImpl();
+  /* Create a descriptor downloader. */
+  public static DescriptorDownloader createDescriptorDownloader() {
+    return new DescriptorDownloaderImpl();
   }
 }
 
diff --git a/src/org/torproject/descriptor/RelayDescriptorDownloader.java b/src/org/torproject/descriptor/RelayDescriptorDownloader.java
deleted file mode 100644
index 29171d3..0000000
--- a/src/org/torproject/descriptor/RelayDescriptorDownloader.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/* Copyright 2011, 2012 The Tor Project
- * See LICENSE for licensing information */
-package org.torproject.descriptor;
-
-import java.util.Iterator;
-import java.util.Set;
-
-/* Download relay descriptors from directory mirrors or authorities. */
-public interface RelayDescriptorDownloader {
-
-  /* Add a directory authority to download descriptors from.  A directory
-   * authority is only required for downloading network status vote and
-   * will be used when no directory mirrors are available. */
-  public void addDirectoryAuthority(String nickname, String ip,
-      int dirPort);
-
-  /* Add a directory mirror to download descriptors from.  Directory
-   * mirrors are preferred when downloading descriptors, except for
-   * network status votes which are only available on directory
-   * authorities. */
-  public void addDirectoryMirror(String nickname, String ip, int dirPort);
-
-  /* Include the current network status consensus in the downloads. */
-  public void setIncludeCurrentConsensus();
-
-  /* Include the current network status consensus in the downloads, and
-   * attempt to download it from all directory authorities.  The primary
-   * purpose of doing this is to compare different consensuses and
-   * download characteristics to each other.  Typically, downloading from
-   * a single directory mirror or authority is sufficient. */
-  public void setIncludeCurrentConsensusFromAllDirectoryAuthorities();
-
-  /* Include the current network status votes referenced from a previously
-   * downloaded consensus in the downloads.  This requires downloading the
-   * current consensus from at least one directory mirror or authority. */
-  public void setIncludeCurrentReferencedVotes();
-
-  /* Include the current network status vote published by the given
-   * directory authority in the downloads.  This requires downloading from
-   * at least one directory authority. */
-  public void setIncludeCurrentVote(String fingerprint);
-
-  /* Include the current network status votes published by the given
-   * directory authorities in the downloads.  This requires downloading
-   * from at least one directory authority. */
-  public void setIncludeCurrentVotes(Set<String> fingerprints);
-
-  /* Include all server descriptors referenced from a previously
-   * downloaded network status consensus in the downloads. */
-  public void setIncludeReferencedServerDescriptors();
-
-  /* Exclude the server descriptor with the given identifier from the
-   * downloads even if it's referenced from a consensus and we're supposed
-   * to download all referenced server descriptors. */
-  public void setExcludeServerDescriptor(String identifier);
-
-  /* Exclude the server descriptors with the given identifiers from the
-   * downloads even if they are referenced from a consensus and we're
-   * supposed to download all referenced server descriptors. */
-  public void setExcludeServerDescriptors(Set<String> identifier);
-
-  /* Include all extra-info descriptors referenced from previously
-   * downloaded server descriptors in the downloads. */
-  public void setIncludeReferencedExtraInfoDescriptors();
-
-  /* Exclude the extra-info descriptor with the given identifier from the
-   * downloads even if it's referenced from a server descriptor and we're
-   * supposed to download all referenced extra-info descriptors. */
-  public void setExcludeExtraInfoDescriptor(String identifier);
-
-  /* Exclude the extra-info descriptors with the given identifiers from
-   * the downloads even if they are referenced from server descriptors
-   * and we're supposed to download all referenced extra-info
-   * descriptors. */
-  public void setExcludeExtraInfoDescriptors(Set<String> identifiers);
-
-  /* Define a connect timeout for a single request.  If a timeout expires,
-   * no further requests will be sent to the directory authority or
-   * mirror.  Setting this value to 0 disables the connect timeout.
-   * Default value is 1 minute (60 * 1000). */
-  public void setConnectTimeout(long connectTimeoutMillis);
-
-  /* Define a read timeout for a single request.  If a timeout expires,
-   * no further requests will be sent to the directory authority or
-   * mirror.  Setting this value to 0 disables the read timeout.
-   * Default value is 1 minute (60 * 1000). */
-  public void setReadTimeout(long readTimeoutMillis);
-
-  /* Define a global timeout for all requests.  Once this timeout expires,
-   * all running requests are aborted and no further requests are made.
-   * Setting this value to 0 disables the global timeout.  Default is 1
-   * hour (60 * 60 * 1000). */
-  public void setGlobalTimeout(long globalTimeoutMillis);
-
-  /* Fail descriptor parsing when encountering an unrecognized line.  This
-   * is not set by default, because the Tor specifications allow for new
-   * lines to be added that shall be ignored by older Tor versions.  But
-   * some applications may want to handle unrecognized descriptor lines
-   * explicitly. */
-  public void setFailUnrecognizedDescriptorLines();
-
-  /* Download the previously configured relay descriptors and make them
-   * available via the returned blocking iterator.  Whenever the
-   * downloader runs out of descriptors and expects to provide more
-   * shortly after, it blocks the caller.  This method can only be run
-   * once. */
-  public Iterator<DescriptorRequest> downloadDescriptors();
-}
-
diff --git a/src/org/torproject/descriptor/RelayDescriptorReader.java b/src/org/torproject/descriptor/RelayDescriptorReader.java
deleted file mode 100644
index 142f79c..0000000
--- a/src/org/torproject/descriptor/RelayDescriptorReader.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/* Copyright 2011, 2012 The Tor Project
- * See LICENSE for licensing information */
-package org.torproject.descriptor;
-
-import java.io.File;
-import java.util.Iterator;
-
-/* Read relay descriptors from one or more local directories. */
-public interface RelayDescriptorReader {
-
-  /* Add a local directory to read relay descriptors from. */
-  public void addDirectory(File directory);
-
-  /* Exclude files that are contained in the given history file and that
-   * haven't changed since they were last read.  Add reads from the
-   * current run to the history file.  Remove files that don't exist
-   * anymore from the history file.  Lines in the history file contain the
-   * last modified timestamp and the absolute path of a file. */
-  public void setExcludeFiles(File historyFile);
-
-  /* Fail descriptor parsing when encountering an unrecognized line.  This
-   * is not set by default, because the Tor specifications allow for new
-   * lines to be added that shall be ignored by older Tor versions.  But
-   * some applications may want to handle unrecognized descriptor lines
-   * explicitly. */
-  public void setFailUnrecognizedDescriptorLines();
-
-  /* Read the previously configured relay descriptors and make them
-   * available via the returned blocking iterator.  Whenever the reader
-   * runs out of descriptors and expects to provide more shortly after, it
-   * blocks the caller.  This method can only be run once. */
-  public Iterator<DescriptorFile> readDescriptors();
-
-}
-
diff --git a/src/org/torproject/descriptor/impl/DescriptorDownloaderImpl.java b/src/org/torproject/descriptor/impl/DescriptorDownloaderImpl.java
new file mode 100644
index 0000000..3c093d1
--- /dev/null
+++ b/src/org/torproject/descriptor/impl/DescriptorDownloaderImpl.java
@@ -0,0 +1,265 @@
+/* Copyright 2011, 2012 The Tor Project
+ * See LICENSE for licensing information */
+package org.torproject.descriptor.impl;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.torproject.descriptor.DescriptorRequest;
+import org.torproject.descriptor.DescriptorDownloader;
+
+public class DescriptorDownloaderImpl
+    implements DescriptorDownloader {
+
+  private boolean hasStartedDownloading = false;
+
+  private SortedMap<String, DirectoryDownloader> directoryAuthorities =
+      new TreeMap<String, DirectoryDownloader>();
+  public void addDirectoryAuthority(String nickname, String ip,
+      int dirPort) {
+    if (this.hasStartedDownloading) {
+      throw new IllegalStateException("Reconfiguration is not permitted "
+          + "after starting to download.");
+    }
+    this.checkDirectoryParameters(nickname, ip, dirPort);
+    DirectoryDownloader directoryAuthority = new DirectoryDownloader(
+        nickname, ip, dirPort);
+    this.directoryAuthorities.put(nickname, directoryAuthority);
+  }
+
+  private SortedMap<String, DirectoryDownloader> directoryMirrors =
+      new TreeMap<String, DirectoryDownloader>();
+  public void addDirectoryMirror(String nickname, String ip,
+      int dirPort) {
+    if (this.hasStartedDownloading) {
+      throw new IllegalStateException("Reconfiguration is not permitted "
+          + "after starting to download.");
+    }
+    this.checkDirectoryParameters(nickname, ip, dirPort);
+    DirectoryDownloader directoryMirror = new DirectoryDownloader(
+        nickname, ip, dirPort);
+    this.directoryMirrors.put(nickname, directoryMirror);
+    /* TODO Implement prioritizing mirrors for non-vote downloads. */
+    throw new UnsupportedOperationException("Prioritizing directory "
+        + "mirrors over directory authorities is not implemented yet.  "
+        + "Until it is, configuring directory mirrors is misleading and "
+        + "therefore not supported.");
+  }
+
+  private void checkDirectoryParameters(String nickname, String ip,
+      int dirPort) {
+    if (nickname == null || nickname.length() < 1) {
+      throw new IllegalArgumentException("'" + nickname + "' is not a "
+          + "valid nickname.");
+    }
+    if (ip == null || ip.length() < 7 || ip.split("\\.").length != 4) {
+      throw new IllegalArgumentException("'" + ip + "' is not a valid IP "
+          + "address.");
+    }
+    if (dirPort < 1 || dirPort > 65535) {
+      throw new IllegalArgumentException(String.valueOf(dirPort) + " is "
+          + "not a valid DirPort.");
+    }
+    /* TODO Relax the requirement for directory nicknames to be unique.
+     * In theory, we can identify them by ip+port. */
+    if (this.directoryAuthorities.containsKey(nickname) ||
+        this.directoryMirrors.containsKey(nickname)) {
+      throw new IllegalArgumentException("Directory nicknames must be "
+          + "unique.");
+    }
+  }
+
+  private boolean downloadConsensus = false;
+  public void setIncludeCurrentConsensus() {
+    if (this.hasStartedDownloading) {
+      throw new IllegalStateException("Reconfiguration is not permitted "
+          + "after starting to download.");
+    }
+    this.downloadConsensus = true;
+  }
+
+  private boolean downloadConsensusFromAllAuthorities = false;
+  public void setIncludeCurrentConsensusFromAllDirectoryAuthorities() {
+    if (this.hasStartedDownloading) {
+      throw new IllegalStateException("Reconfiguration is not permitted "
+          + "after starting to download.");
+    }
+    this.downloadConsensusFromAllAuthorities = true;
+  }
+
+  private boolean includeCurrentReferencedVotes = false;
+  public void setIncludeCurrentReferencedVotes() {
+    if (this.hasStartedDownloading) {
+      throw new IllegalStateException("Reconfiguration is not permitted "
+          + "after starting to download.");
+    }
+    this.includeCurrentReferencedVotes = true;
+  }
+
+  private Set<String> downloadVotes = new HashSet<String>();
+  public void setIncludeCurrentVote(String fingerprint) {
+    if (this.hasStartedDownloading) {
+      throw new IllegalStateException("Reconfiguration is not permitted "
+          + "after starting to download.");
+    }
+    this.checkVoteFingerprint(fingerprint);
+    this.downloadVotes.add(fingerprint);
+  }
+
+  public void setIncludeCurrentVotes(Set<String> fingerprints) {
+    if (this.hasStartedDownloading) {
+      throw new IllegalStateException("Reconfiguration is not permitted "
+          + "after starting to download.");
+    }
+    if (fingerprints == null) {
+      throw new IllegalArgumentException("Set of fingerprints must not "
+          + "be null.");
+    }
+    for (String fingerprint : fingerprints) {
+      this.checkVoteFingerprint(fingerprint);
+    }
+    for (String fingerprint : fingerprints) {
+      this.setIncludeCurrentVote(fingerprint);
+    }
+  }
+
+  private void checkVoteFingerprint(String fingerprint) {
+    if (fingerprint == null || fingerprint.length() != 40) {
+      throw new IllegalArgumentException("'" + fingerprint + "' is not a "
+          + "valid fingerprint.");
+    }
+  }
+
+  public void setIncludeReferencedServerDescriptors() {
+    if (this.hasStartedDownloading) {
+      throw new IllegalStateException("Reconfiguration is not permitted "
+          + "after starting to download.");
+    }
+    /* TODO Implement me. */
+    throw new UnsupportedOperationException("Downloading server "
+        + "descriptors is not implemented yet.");
+  }
+
+  public void setExcludeServerDescriptor(String identifier) {
+    if (this.hasStartedDownloading) {
+      throw new IllegalStateException("Reconfiguration is not permitted "
+          + "after starting to download.");
+    }
+    /* TODO Implement me. */
+    throw new UnsupportedOperationException("Downloading server "
+        + "descriptors is not implemented yet.");
+  }
+
+  public void setExcludeServerDescriptors(Set<String> identifier) {
+    if (this.hasStartedDownloading) {
+      throw new IllegalStateException("Reconfiguration is not permitted "
+          + "after starting to download.");
+    }
+    /* TODO Implement me. */
+    throw new UnsupportedOperationException("Downloading server "
+        + "descriptors is not implemented yet.");
+  }
+
+  public void setIncludeReferencedExtraInfoDescriptors() {
+    if (this.hasStartedDownloading) {
+      throw new IllegalStateException("Reconfiguration is not permitted "
+          + "after starting to download.");
+    }
+    /* TODO Implement me. */
+    throw new UnsupportedOperationException("Downloading extra-info "
+        + "descriptors is not implemented yet.");
+  }
+
+  public void setExcludeExtraInfoDescriptor(String identifier) {
+    if (this.hasStartedDownloading) {
+      throw new IllegalStateException("Reconfiguration is not permitted "
+          + "after starting to download.");
+    }
+    /* TODO Implement me. */
+    throw new UnsupportedOperationException("Downloading extra-info "
+        + "descriptors is not implemented yet.");
+  }
+
+  public void setExcludeExtraInfoDescriptors(Set<String> identifiers) {
+    if (this.hasStartedDownloading) {
+      throw new IllegalStateException("Reconfiguration is not permitted "
+          + "after starting to download.");
+    }
+    /* TODO Implement me. */
+    throw new UnsupportedOperationException("Downloading extra-info "
+        + "descriptors is not implemented yet.");
+  }
+
+  private long readTimeoutMillis = 60L * 1000L;
+  public void setReadTimeout(long readTimeoutMillis) {
+    if (this.hasStartedDownloading) {
+      throw new IllegalStateException("Reconfiguration is not permitted "
+          + "after starting to download.");
+    }
+    if (readTimeoutMillis < 0L) {
+      throw new IllegalArgumentException("Read timeout value "
+          + String.valueOf(readTimeoutMillis) + " may not be "
+          + "negative.");
+    }
+    this.readTimeoutMillis = readTimeoutMillis;
+  }
+
+  private long connectTimeoutMillis = 60L * 1000L;
+  public void setConnectTimeout(long connectTimeoutMillis) {
+    if (this.hasStartedDownloading) {
+      throw new IllegalStateException("Reconfiguration is not permitted "
+          + "after starting to download.");
+    }
+    if (connectTimeoutMillis < 0L) {
+      throw new IllegalArgumentException("Connect timeout value "
+          + String.valueOf(connectTimeoutMillis) + " may not be "
+          + "negative.");
+    }
+    this.connectTimeoutMillis = connectTimeoutMillis;
+  }
+
+  private long globalTimeoutMillis = 60L * 60L * 1000L;
+  public void setGlobalTimeout(long globalTimeoutMillis) {
+    if (this.hasStartedDownloading) {
+      throw new IllegalStateException("Reconfiguration is not permitted "
+          + "after starting to download.");
+    }
+    if (globalTimeoutMillis < 0L) {
+      throw new IllegalArgumentException("Global timeout value "
+          + String.valueOf(globalTimeoutMillis) + " may not be "
+          + "negative.");
+    }
+    this.globalTimeoutMillis = globalTimeoutMillis;
+  }
+
+  private boolean failUnrecognizedDescriptorLines = false;
+  public void setFailUnrecognizedDescriptorLines() {
+    if (this.hasStartedDownloading) {
+      throw new IllegalStateException("Reconfiguration is not permitted "
+          + "after starting to download.");
+    }
+    this.failUnrecognizedDescriptorLines = true;
+  }
+
+  public Iterator<DescriptorRequest> downloadDescriptors() {
+    if (this.hasStartedDownloading) {
+      throw new IllegalStateException("Initiating downloads is only "
+          + "permitted once.");
+    }
+    this.hasStartedDownloading = true;
+    DownloadCoordinatorImpl downloadCoordinator =
+        new DownloadCoordinatorImpl(this.directoryAuthorities,
+        this.directoryMirrors, this.downloadConsensus,
+        this.downloadConsensusFromAllAuthorities, this.downloadVotes,
+        this.includeCurrentReferencedVotes, this.connectTimeoutMillis,
+        this.readTimeoutMillis, this.globalTimeoutMillis,
+        this.failUnrecognizedDescriptorLines);
+    Iterator<DescriptorRequest> descriptorQueue = downloadCoordinator.
+        getDescriptorQueue();
+    return descriptorQueue;
+  }
+}
+
diff --git a/src/org/torproject/descriptor/impl/DescriptorReaderImpl.java b/src/org/torproject/descriptor/impl/DescriptorReaderImpl.java
new file mode 100644
index 0000000..c22cbe8
--- /dev/null
+++ b/src/org/torproject/descriptor/impl/DescriptorReaderImpl.java
@@ -0,0 +1,194 @@
+/* Copyright 2011, 2012 The Tor Project
+ * See LICENSE for licensing information */
+package org.torproject.descriptor.impl;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.Stack;
+import java.util.TreeMap;
+
+import org.torproject.descriptor.Descriptor;
+import org.torproject.descriptor.DescriptorFile;
+import org.torproject.descriptor.DescriptorReader;
+
+public class DescriptorReaderImpl implements DescriptorReader {
+
+  private boolean hasStartedReading = false;
+
+  private List<File> directories = new ArrayList<File>();
+  public void addDirectory(File directory) {
+    if (this.hasStartedReading) {
+      throw new IllegalStateException("Reconfiguration is not permitted "
+          + "after starting to read.");
+    }
+    this.directories.add(directory);
+  }
+
+  private File historyFile;
+  public void setExcludeFiles(File historyFile) {
+    if (this.hasStartedReading) {
+      throw new IllegalStateException("Reconfiguration is not permitted "
+          + "after starting to read.");
+    }
+    this.historyFile = historyFile;
+  }
+
+  private boolean failUnrecognizedDescriptorLines = false;
+  public void setFailUnrecognizedDescriptorLines() {
+    if (this.hasStartedReading) {
+      throw new IllegalStateException("Reconfiguration is not permitted "
+          + "after starting to read.");
+    }
+    this.failUnrecognizedDescriptorLines = true;
+  }
+
+  public Iterator<DescriptorFile> readDescriptors() {
+    if (this.hasStartedReading) {
+      throw new IllegalStateException("Initiating reading is only "
+          + "permitted once.");
+    }
+    this.hasStartedReading = true;
+    BlockingIteratorImpl<DescriptorFile> descriptorQueue =
+        new BlockingIteratorImpl<DescriptorFile>();
+    DescriptorReaderRunnable reader = new DescriptorReaderRunnable(
+        this.directories, descriptorQueue, this.historyFile,
+        this.failUnrecognizedDescriptorLines);
+    new Thread(reader).start();
+    return descriptorQueue;
+  }
+
+  private static class DescriptorReaderRunnable implements Runnable {
+    private List<File> directories;
+    private BlockingIteratorImpl<DescriptorFile> descriptorQueue;
+    private File historyFile;
+    private boolean failUnrecognizedDescriptorLines;
+    private DescriptorReaderRunnable(List<File> directories,
+        BlockingIteratorImpl<DescriptorFile> descriptorQueue,
+        File historyFile, boolean failUnrecognizedDescriptorLines) {
+      this.directories = directories;
+      this.descriptorQueue = descriptorQueue;
+      this.historyFile = historyFile;
+      this.failUnrecognizedDescriptorLines =
+          failUnrecognizedDescriptorLines;
+    }
+    public void run() {
+      this.readOldHistory();
+      this.readDescriptors();
+      this.writeNewHistory();
+    }
+    private SortedMap<String, Long>
+        oldHistory = new TreeMap<String, Long>(),
+        newHistory = new TreeMap<String, Long>();
+    private void readOldHistory() {
+      if (this.historyFile == null) {
+        return;
+      }
+      try {
+        BufferedReader br = new BufferedReader(new FileReader(
+            this.historyFile));
+        String line;
+        while ((line = br.readLine()) != null) {
+          if (!line.contains(" ")) {
+            /* TODO Handle this problem? */
+            continue;
+          }
+          long lastModifiedMillis = Long.parseLong(line.substring(0,
+              line.indexOf(" ")));
+          String absolutePath = line.substring(line.indexOf(" ") + 1);
+          this.oldHistory.put(absolutePath, lastModifiedMillis);
+        }
+        br.close();
+      } catch (IOException e) {
+        /* TODO Handle this exception. */
+      } catch (NumberFormatException e) {
+        /* TODO Handle this exception. */
+      }
+    }
+    private void writeNewHistory() {
+      if (this.historyFile == null) {
+        return;
+      }
+      try {
+        if (this.historyFile.getParentFile() != null) {
+          this.historyFile.getParentFile().mkdirs();
+        }
+        BufferedWriter bw = new BufferedWriter(new FileWriter(
+            this.historyFile));
+        for (Map.Entry<String, Long> e : this.newHistory.entrySet()) {
+          String absolutePath = e.getKey();
+          long lastModifiedMillis = e.getValue();
+          bw.write(String.valueOf(lastModifiedMillis) + " " + absolutePath
+              + "\n");
+        }
+        bw.close();
+      } catch (IOException e) {
+        /* TODO Handle this exception. */
+      }
+    }
+    private void readDescriptors() {
+      for (File directory : this.directories) {
+        Stack<File> files = new Stack<File>();
+        files.add(directory);
+        boolean abortReading = false;
+        while (!abortReading && !files.isEmpty()) {
+          File file = files.pop();
+          if (file.isDirectory()) {
+            files.addAll(Arrays.asList(file.listFiles()));
+          } else {
+            String absolutePath = file.getAbsolutePath();
+            long lastModifiedMillis = file.lastModified();
+            this.newHistory.put(absolutePath, lastModifiedMillis);
+            if (this.oldHistory.containsKey(absolutePath) &&
+                this.oldHistory.get(absolutePath) == lastModifiedMillis) {
+              continue;
+            }
+            DescriptorFileImpl descriptorFile = new DescriptorFileImpl();
+            try {
+              descriptorFile.setDirectory(directory);
+              descriptorFile.setFile(file);
+              descriptorFile.setLastModified(lastModifiedMillis);
+              descriptorFile.setDescriptors(this.readFile(file));
+            } catch (DescriptorParseException e) {
+              descriptorFile.setException(e);
+            } catch (IOException e) {
+              descriptorFile.setException(e);
+              abortReading = true;
+            }
+            this.descriptorQueue.add(descriptorFile);
+          }
+        }
+      }
+      this.descriptorQueue.setOutOfDescriptors();
+    }
+    private List<Descriptor> readFile(File file) throws IOException,
+        DescriptorParseException {
+      FileInputStream fis = new FileInputStream(file);
+      BufferedInputStream bis = new BufferedInputStream(fis);
+      ByteArrayOutputStream baos = new ByteArrayOutputStream();
+      int len;
+      byte[] data = new byte[1024];
+      while ((len = bis.read(data, 0, 1024)) >= 0) {
+        baos.write(data, 0, len);
+      }
+      bis.close();
+      byte[] rawDescriptorBytes = baos.toByteArray();
+      return DescriptorImpl.parseRelayOrBridgeDescriptors(
+          rawDescriptorBytes, file.getName(),
+          this.failUnrecognizedDescriptorLines);
+    }
+  }
+}
+
diff --git a/src/org/torproject/descriptor/impl/RelayDescriptorDownloaderImpl.java b/src/org/torproject/descriptor/impl/RelayDescriptorDownloaderImpl.java
deleted file mode 100644
index 6993509..0000000
--- a/src/org/torproject/descriptor/impl/RelayDescriptorDownloaderImpl.java
+++ /dev/null
@@ -1,265 +0,0 @@
-/* Copyright 2011, 2012 The Tor Project
- * See LICENSE for licensing information */
-package org.torproject.descriptor.impl;
-
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import org.torproject.descriptor.DescriptorRequest;
-import org.torproject.descriptor.RelayDescriptorDownloader;
-
-public class RelayDescriptorDownloaderImpl
-    implements RelayDescriptorDownloader {
-
-  private boolean hasStartedDownloading = false;
-
-  private SortedMap<String, DirectoryDownloader> directoryAuthorities =
-      new TreeMap<String, DirectoryDownloader>();
-  public void addDirectoryAuthority(String nickname, String ip,
-      int dirPort) {
-    if (this.hasStartedDownloading) {
-      throw new IllegalStateException("Reconfiguration is not permitted "
-          + "after starting to download.");
-    }
-    this.checkDirectoryParameters(nickname, ip, dirPort);
-    DirectoryDownloader directoryAuthority = new DirectoryDownloader(
-        nickname, ip, dirPort);
-    this.directoryAuthorities.put(nickname, directoryAuthority);
-  }
-
-  private SortedMap<String, DirectoryDownloader> directoryMirrors =
-      new TreeMap<String, DirectoryDownloader>();
-  public void addDirectoryMirror(String nickname, String ip,
-      int dirPort) {
-    if (this.hasStartedDownloading) {
-      throw new IllegalStateException("Reconfiguration is not permitted "
-          + "after starting to download.");
-    }
-    this.checkDirectoryParameters(nickname, ip, dirPort);
-    DirectoryDownloader directoryMirror = new DirectoryDownloader(
-        nickname, ip, dirPort);
-    this.directoryMirrors.put(nickname, directoryMirror);
-    /* TODO Implement prioritizing mirrors for non-vote downloads. */
-    throw new UnsupportedOperationException("Prioritizing directory "
-        + "mirrors over directory authorities is not implemented yet.  "
-        + "Until it is, configuring directory mirrors is misleading and "
-        + "therefore not supported.");
-  }
-
-  private void checkDirectoryParameters(String nickname, String ip,
-      int dirPort) {
-    if (nickname == null || nickname.length() < 1) {
-      throw new IllegalArgumentException("'" + nickname + "' is not a "
-          + "valid nickname.");
-    }
-    if (ip == null || ip.length() < 7 || ip.split("\\.").length != 4) {
-      throw new IllegalArgumentException("'" + ip + "' is not a valid IP "
-          + "address.");
-    }
-    if (dirPort < 1 || dirPort > 65535) {
-      throw new IllegalArgumentException(String.valueOf(dirPort) + " is "
-          + "not a valid DirPort.");
-    }
-    /* TODO Relax the requirement for directory nicknames to be unique.
-     * In theory, we can identify them by ip+port. */
-    if (this.directoryAuthorities.containsKey(nickname) ||
-        this.directoryMirrors.containsKey(nickname)) {
-      throw new IllegalArgumentException("Directory nicknames must be "
-          + "unique.");
-    }
-  }
-
-  private boolean downloadConsensus = false;
-  public void setIncludeCurrentConsensus() {
-    if (this.hasStartedDownloading) {
-      throw new IllegalStateException("Reconfiguration is not permitted "
-          + "after starting to download.");
-    }
-    this.downloadConsensus = true;
-  }
-
-  private boolean downloadConsensusFromAllAuthorities = false;
-  public void setIncludeCurrentConsensusFromAllDirectoryAuthorities() {
-    if (this.hasStartedDownloading) {
-      throw new IllegalStateException("Reconfiguration is not permitted "
-          + "after starting to download.");
-    }
-    this.downloadConsensusFromAllAuthorities = true;
-  }
-
-  private boolean includeCurrentReferencedVotes = false;
-  public void setIncludeCurrentReferencedVotes() {
-    if (this.hasStartedDownloading) {
-      throw new IllegalStateException("Reconfiguration is not permitted "
-          + "after starting to download.");
-    }
-    this.includeCurrentReferencedVotes = true;
-  }
-
-  private Set<String> downloadVotes = new HashSet<String>();
-  public void setIncludeCurrentVote(String fingerprint) {
-    if (this.hasStartedDownloading) {
-      throw new IllegalStateException("Reconfiguration is not permitted "
-          + "after starting to download.");
-    }
-    this.checkVoteFingerprint(fingerprint);
-    this.downloadVotes.add(fingerprint);
-  }
-
-  public void setIncludeCurrentVotes(Set<String> fingerprints) {
-    if (this.hasStartedDownloading) {
-      throw new IllegalStateException("Reconfiguration is not permitted "
-          + "after starting to download.");
-    }
-    if (fingerprints == null) {
-      throw new IllegalArgumentException("Set of fingerprints must not "
-          + "be null.");
-    }
-    for (String fingerprint : fingerprints) {
-      this.checkVoteFingerprint(fingerprint);
-    }
-    for (String fingerprint : fingerprints) {
-      this.setIncludeCurrentVote(fingerprint);
-    }
-  }
-
-  private void checkVoteFingerprint(String fingerprint) {
-    if (fingerprint == null || fingerprint.length() != 40) {
-      throw new IllegalArgumentException("'" + fingerprint + "' is not a "
-          + "valid fingerprint.");
-    }
-  }
-
-  public void setIncludeReferencedServerDescriptors() {
-    if (this.hasStartedDownloading) {
-      throw new IllegalStateException("Reconfiguration is not permitted "
-          + "after starting to download.");
-    }
-    /* TODO Implement me. */
-    throw new UnsupportedOperationException("Downloading server "
-        + "descriptors is not implemented yet.");
-  }
-
-  public void setExcludeServerDescriptor(String identifier) {
-    if (this.hasStartedDownloading) {
-      throw new IllegalStateException("Reconfiguration is not permitted "
-          + "after starting to download.");
-    }
-    /* TODO Implement me. */
-    throw new UnsupportedOperationException("Downloading server "
-        + "descriptors is not implemented yet.");
-  }
-
-  public void setExcludeServerDescriptors(Set<String> identifier) {
-    if (this.hasStartedDownloading) {
-      throw new IllegalStateException("Reconfiguration is not permitted "
-          + "after starting to download.");
-    }
-    /* TODO Implement me. */
-    throw new UnsupportedOperationException("Downloading server "
-        + "descriptors is not implemented yet.");
-  }
-
-  public void setIncludeReferencedExtraInfoDescriptors() {
-    if (this.hasStartedDownloading) {
-      throw new IllegalStateException("Reconfiguration is not permitted "
-          + "after starting to download.");
-    }
-    /* TODO Implement me. */
-    throw new UnsupportedOperationException("Downloading extra-info "
-        + "descriptors is not implemented yet.");
-  }
-
-  public void setExcludeExtraInfoDescriptor(String identifier) {
-    if (this.hasStartedDownloading) {
-      throw new IllegalStateException("Reconfiguration is not permitted "
-          + "after starting to download.");
-    }
-    /* TODO Implement me. */
-    throw new UnsupportedOperationException("Downloading extra-info "
-        + "descriptors is not implemented yet.");
-  }
-
-  public void setExcludeExtraInfoDescriptors(Set<String> identifiers) {
-    if (this.hasStartedDownloading) {
-      throw new IllegalStateException("Reconfiguration is not permitted "
-          + "after starting to download.");
-    }
-    /* TODO Implement me. */
-    throw new UnsupportedOperationException("Downloading extra-info "
-        + "descriptors is not implemented yet.");
-  }
-
-  private long readTimeoutMillis = 60L * 1000L;
-  public void setReadTimeout(long readTimeoutMillis) {
-    if (this.hasStartedDownloading) {
-      throw new IllegalStateException("Reconfiguration is not permitted "
-          + "after starting to download.");
-    }
-    if (readTimeoutMillis < 0L) {
-      throw new IllegalArgumentException("Read timeout value "
-          + String.valueOf(readTimeoutMillis) + " may not be "
-          + "negative.");
-    }
-    this.readTimeoutMillis = readTimeoutMillis;
-  }
-
-  private long connectTimeoutMillis = 60L * 1000L;
-  public void setConnectTimeout(long connectTimeoutMillis) {
-    if (this.hasStartedDownloading) {
-      throw new IllegalStateException("Reconfiguration is not permitted "
-          + "after starting to download.");
-    }
-    if (connectTimeoutMillis < 0L) {
-      throw new IllegalArgumentException("Connect timeout value "
-          + String.valueOf(connectTimeoutMillis) + " may not be "
-          + "negative.");
-    }
-    this.connectTimeoutMillis = connectTimeoutMillis;
-  }
-
-  private long globalTimeoutMillis = 60L * 60L * 1000L;
-  public void setGlobalTimeout(long globalTimeoutMillis) {
-    if (this.hasStartedDownloading) {
-      throw new IllegalStateException("Reconfiguration is not permitted "
-          + "after starting to download.");
-    }
-    if (globalTimeoutMillis < 0L) {
-      throw new IllegalArgumentException("Global timeout value "
-          + String.valueOf(globalTimeoutMillis) + " may not be "
-          + "negative.");
-    }
-    this.globalTimeoutMillis = globalTimeoutMillis;
-  }
-
-  private boolean failUnrecognizedDescriptorLines = false;
-  public void setFailUnrecognizedDescriptorLines() {
-    if (this.hasStartedDownloading) {
-      throw new IllegalStateException("Reconfiguration is not permitted "
-          + "after starting to download.");
-    }
-    this.failUnrecognizedDescriptorLines = true;
-  }
-
-  public Iterator<DescriptorRequest> downloadDescriptors() {
-    if (this.hasStartedDownloading) {
-      throw new IllegalStateException("Initiating downloads is only "
-          + "permitted once.");
-    }
-    this.hasStartedDownloading = true;
-    DownloadCoordinatorImpl downloadCoordinator =
-        new DownloadCoordinatorImpl(this.directoryAuthorities,
-        this.directoryMirrors, this.downloadConsensus,
-        this.downloadConsensusFromAllAuthorities, this.downloadVotes,
-        this.includeCurrentReferencedVotes, this.connectTimeoutMillis,
-        this.readTimeoutMillis, this.globalTimeoutMillis,
-        this.failUnrecognizedDescriptorLines);
-    Iterator<DescriptorRequest> descriptorQueue = downloadCoordinator.
-        getDescriptorQueue();
-    return descriptorQueue;
-  }
-}
-
diff --git a/src/org/torproject/descriptor/impl/RelayOrBridgeDescriptorReaderImpl.java b/src/org/torproject/descriptor/impl/RelayOrBridgeDescriptorReaderImpl.java
deleted file mode 100644
index 60c1e36..0000000
--- a/src/org/torproject/descriptor/impl/RelayOrBridgeDescriptorReaderImpl.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/* Copyright 2011, 2012 The Tor Project
- * See LICENSE for licensing information */
-package org.torproject.descriptor.impl;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.SortedMap;
-import java.util.Stack;
-import java.util.TreeMap;
-
-import org.torproject.descriptor.BridgeDescriptorReader;
-import org.torproject.descriptor.BridgePoolAssignmentReader;
-import org.torproject.descriptor.Descriptor;
-import org.torproject.descriptor.DescriptorFile;
-import org.torproject.descriptor.RelayDescriptorReader;
-
-public class RelayOrBridgeDescriptorReaderImpl
-    implements RelayDescriptorReader, BridgeDescriptorReader,
-    BridgePoolAssignmentReader {
-
-  private boolean hasStartedReading = false;
-
-  private List<File> directories = new ArrayList<File>();
-  public void addDirectory(File directory) {
-    if (this.hasStartedReading) {
-      throw new IllegalStateException("Reconfiguration is not permitted "
-          + "after starting to read.");
-    }
-    this.directories.add(directory);
-  }
-
-  private File historyFile;
-  public void setExcludeFiles(File historyFile) {
-    if (this.hasStartedReading) {
-      throw new IllegalStateException("Reconfiguration is not permitted "
-          + "after starting to read.");
-    }
-    this.historyFile = historyFile;
-  }
-
-  private boolean failUnrecognizedDescriptorLines = false;
-  public void setFailUnrecognizedDescriptorLines() {
-    if (this.hasStartedReading) {
-      throw new IllegalStateException("Reconfiguration is not permitted "
-          + "after starting to read.");
-    }
-    this.failUnrecognizedDescriptorLines = true;
-  }
-
-  public Iterator<DescriptorFile> readDescriptors() {
-    if (this.hasStartedReading) {
-      throw new IllegalStateException("Initiating reading is only "
-          + "permitted once.");
-    }
-    this.hasStartedReading = true;
-    BlockingIteratorImpl<DescriptorFile> descriptorQueue =
-        new BlockingIteratorImpl<DescriptorFile>();
-    DescriptorReader reader = new DescriptorReader(this.directories,
-        descriptorQueue, this.historyFile,
-        this.failUnrecognizedDescriptorLines);
-    new Thread(reader).start();
-    return descriptorQueue;
-  }
-
-  private static class DescriptorReader implements Runnable {
-    private List<File> directories;
-    private BlockingIteratorImpl<DescriptorFile> descriptorQueue;
-    private File historyFile;
-    private boolean failUnrecognizedDescriptorLines;
-    private DescriptorReader(List<File> directories,
-        BlockingIteratorImpl<DescriptorFile> descriptorQueue,
-        File historyFile, boolean failUnrecognizedDescriptorLines) {
-      this.directories = directories;
-      this.descriptorQueue = descriptorQueue;
-      this.historyFile = historyFile;
-      this.failUnrecognizedDescriptorLines =
-          failUnrecognizedDescriptorLines;
-    }
-    public void run() {
-      this.readOldHistory();
-      this.readDescriptors();
-      this.writeNewHistory();
-    }
-    private SortedMap<String, Long>
-        oldHistory = new TreeMap<String, Long>(),
-        newHistory = new TreeMap<String, Long>();
-    private void readOldHistory() {
-      if (this.historyFile == null) {
-        return;
-      }
-      try {
-        BufferedReader br = new BufferedReader(new FileReader(
-            this.historyFile));
-        String line;
-        while ((line = br.readLine()) != null) {
-          if (!line.contains(" ")) {
-            /* TODO Handle this problem? */
-            continue;
-          }
-          long lastModifiedMillis = Long.parseLong(line.substring(0,
-              line.indexOf(" ")));
-          String absolutePath = line.substring(line.indexOf(" ") + 1);
-          this.oldHistory.put(absolutePath, lastModifiedMillis);
-        }
-        br.close();
-      } catch (IOException e) {
-        /* TODO Handle this exception. */
-      } catch (NumberFormatException e) {
-        /* TODO Handle this exception. */
-      }
-    }
-    private void writeNewHistory() {
-      if (this.historyFile == null) {
-        return;
-      }
-      try {
-        if (this.historyFile.getParentFile() != null) {
-          this.historyFile.getParentFile().mkdirs();
-        }
-        BufferedWriter bw = new BufferedWriter(new FileWriter(
-            this.historyFile));
-        for (Map.Entry<String, Long> e : this.newHistory.entrySet()) {
-          String absolutePath = e.getKey();
-          long lastModifiedMillis = e.getValue();
-          bw.write(String.valueOf(lastModifiedMillis) + " " + absolutePath
-              + "\n");
-        }
-        bw.close();
-      } catch (IOException e) {
-        /* TODO Handle this exception. */
-      }
-    }
-    private void readDescriptors() {
-      for (File directory : this.directories) {
-        Stack<File> files = new Stack<File>();
-        files.add(directory);
-        boolean abortReading = false;
-        while (!abortReading && !files.isEmpty()) {
-          File file = files.pop();
-          if (file.isDirectory()) {
-            files.addAll(Arrays.asList(file.listFiles()));
-          } else {
-            String absolutePath = file.getAbsolutePath();
-            long lastModifiedMillis = file.lastModified();
-            this.newHistory.put(absolutePath, lastModifiedMillis);
-            if (this.oldHistory.containsKey(absolutePath) &&
-                this.oldHistory.get(absolutePath) == lastModifiedMillis) {
-              continue;
-            }
-            DescriptorFileImpl descriptorFile = new DescriptorFileImpl();
-            try {
-              descriptorFile.setDirectory(directory);
-              descriptorFile.setFile(file);
-              descriptorFile.setLastModified(lastModifiedMillis);
-              descriptorFile.setDescriptors(this.readFile(file));
-            } catch (DescriptorParseException e) {
-              descriptorFile.setException(e);
-            } catch (IOException e) {
-              descriptorFile.setException(e);
-              abortReading = true;
-            }
-            this.descriptorQueue.add(descriptorFile);
-          }
-        }
-      }
-      this.descriptorQueue.setOutOfDescriptors();
-    }
-    private List<Descriptor> readFile(File file) throws IOException,
-        DescriptorParseException {
-      FileInputStream fis = new FileInputStream(file);
-      BufferedInputStream bis = new BufferedInputStream(fis);
-      ByteArrayOutputStream baos = new ByteArrayOutputStream();
-      int len;
-      byte[] data = new byte[1024];
-      while ((len = bis.read(data, 0, 1024)) >= 0) {
-        baos.write(data, 0, len);
-      }
-      bis.close();
-      byte[] rawDescriptorBytes = baos.toByteArray();
-      return DescriptorImpl.parseRelayOrBridgeDescriptors(
-          rawDescriptorBytes, file.getName(),
-          this.failUnrecognizedDescriptorLines);
-    }
-  }
-}
-





More information about the tor-commits mailing list