[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[vidalia-svn] r3768: Switch to fetching and parsing the more extensible key=val G (in vidalia/trunk/src/vidalia: . network)



Author: edmanm
Date: 2009-05-13 15:07:26 -0400 (Wed, 13 May 2009)
New Revision: 3768

Modified:
   vidalia/trunk/src/vidalia/CMakeLists.txt
   vidalia/trunk/src/vidalia/network/GeoIp.cpp
   vidalia/trunk/src/vidalia/network/GeoIp.h
   vidalia/trunk/src/vidalia/network/GeoIpCache.cpp
   vidalia/trunk/src/vidalia/network/GeoIpCache.h
   vidalia/trunk/src/vidalia/network/GeoIpCacheItem.cpp
   vidalia/trunk/src/vidalia/network/GeoIpCacheItem.h
   vidalia/trunk/src/vidalia/network/GeoIpRequest.cpp
   vidalia/trunk/src/vidalia/network/GeoIpRequest.h
   vidalia/trunk/src/vidalia/network/GeoIpResolver.cpp
   vidalia/trunk/src/vidalia/network/GeoIpResolver.h
   vidalia/trunk/src/vidalia/network/GeoIpResponse.cpp
   vidalia/trunk/src/vidalia/network/GeoIpResponse.h
   vidalia/trunk/src/vidalia/network/NetViewer.cpp
   vidalia/trunk/src/vidalia/network/RouterListItem.cpp
Log:

Switch to fetching and parsing the more extensible key=val GeoIP response
format, add support for associating an geographic location with a block of
IP addresses rather than a single address, add support for long region and
country names rather than just two-letter codes, and const-ify some method
arguments.


Modified: vidalia/trunk/src/vidalia/CMakeLists.txt
===================================================================
--- vidalia/trunk/src/vidalia/CMakeLists.txt	2009-05-13 18:54:28 UTC (rev 3767)
+++ vidalia/trunk/src/vidalia/CMakeLists.txt	2009-05-13 19:07:26 UTC (rev 3768)
@@ -183,6 +183,7 @@
 )
 qt4_wrap_cpp(vidalia_SRCS
   network/CircuitListWidget.h
+  network/GeoIpCache.h
   network/GeoIpResolver.h
   network/NetViewer.h
   network/RouterDescriptorView.h

Modified: vidalia/trunk/src/vidalia/network/GeoIp.cpp
===================================================================
--- vidalia/trunk/src/vidalia/network/GeoIp.cpp	2009-05-13 18:54:28 UTC (rev 3767)
+++ vidalia/trunk/src/vidalia/network/GeoIp.cpp	2009-05-13 19:07:26 UTC (rev 3768)
@@ -24,116 +24,52 @@
 #define IS_VALID_LONGITUDE(x)   (((x) >= -180.0) && ((x) <= 180.0))
 
 
-/** Constructor */
-GeoIp::GeoIp(QHostAddress ip)
+GeoIp::GeoIp()
 {
-  _ip = ip;
-  _latitude = _longitude = 0xFFFF;
+  _latitude  = 0.0;
+  _longitude = 0.0;
 }
 
-/** Constructor. */
-GeoIp::GeoIp(QHostAddress ip, float latitude, float longitude, 
-             QString city, QString state, QString country)
+GeoIp::GeoIp(const QHostAddress &ip, float latitude, float longitude,
+             const QString &city, const QString &region,
+             const QString &country, const QString &countryCode) 
 {
-  _ip        = ip;
-  _latitude  = latitude;
+  _ip = ip;
+  _latitude = latitude;
   _longitude = longitude;
-  _city      = city;
-  _state     = state;
-  _country   = country;
+  _city = city;
+  _region = region;
+  _country = country;
+  _countryCode = countryCode;
 }
 
-/** Parses the GeoIp information from a comma-delimited string. The format of
- * the string is as in the following example:
- *
- *      128.213.48.13,Troy,NY,US,42.7495,-73.5951,1138402852
- */
-GeoIp
-GeoIp::fromString(QString geoip)
+bool
+GeoIp::isValid() const
 {
-  /* Split comma-delimited data fields */
-  QStringList data = geoip.split(",");
-  
-  if (data.size() == 2 && data.at(1).toLower() == "unknown") {
-    return GeoIp(QHostAddress(data.at(0)));
-  } else if (data.size() < 6) {
-    return GeoIp();
-  }
-  
-  /* Parse the data from the string */
-  QHostAddress   ip(data.at(0));
-  QString city    = data.at(1);
-  QString state   = data.at(2);
-  QString country = data.at(3);
-  float latitude  = data.at(4).toFloat();
-  float longitude = data.at(5).toFloat();
- 
-  /* Create a new GeoIp object with the parsed data. */
-  return GeoIp(ip, latitude, longitude, city, state, country);
+  return (! _ip.isNull()
+            && IS_VALID_LATITUDE(_latitude)
+            && IS_VALID_LONGITUDE(_longitude));
 }
 
-/** Formats the GeoIp information as a comma-delimited string. */
 QString
 GeoIp::toString() const
 {
-  QString s;
-  /* Assemble and comma-delimit the data fields */
-  s.append(_ip.toString());
-  s.append("," + _city);
-  s.append("," + _state);
-  s.append("," + _country);
-  s.append("," + QString::number(_latitude,  'f', 4));
-  s.append("," + QString::number(_longitude, 'f', 4));
-  return s;
-}
-
-/** Returns true if the GeoIp object is invalid. */
-bool
-GeoIp::isEmpty() const
-{
-  return (_ip.isNull() && 
-          !IS_VALID_LATITUDE(_latitude) && 
-          !IS_VALID_LONGITUDE(_longitude));
-}
-
-/** Returns true if the GeoIp object is valid, but no location information
- * is known for the associated IP address. */
-bool
-GeoIp::isUnknown() const
-{
-  return (!_ip.isNull() && 
-          !IS_VALID_LATITUDE(_latitude) && 
-          !IS_VALID_LONGITUDE(_longitude));
-}
-
-/** Returns a human-readable string of GeoIp location information. */
-QString
-GeoIp::toLocation() const
-{
   QStringList location;
-  
+
   /* Add the city name (if present) */
-  if (!_city.isEmpty()) {
+  if (!_city.isEmpty())
     location << _city;
-  }
-  /* Add the state or region name (if present) */
-  if (!_state.isEmpty()) {
-    /* Only display non-numeric region codes. */
-    bool valid = true;
-    for (int i = 0; i < _state.length(); i++) {
-      if (_state[i].isDigit()) {
-        valid = false;
-        break;
-      }
-    }
-    if (valid) {
-      location << _state;
-    }
-  }
-  /* Add the country code (if present) */
-  if (!_country.isEmpty()) {
+
+  /* Add the full state or region name (if present) */
+  if (!_region.isEmpty() && _region != _city)
+      location << _region;
+
+  /* Add the country name or the country code (if present) */
+  if (!_country.isEmpty())
     location << _country;
-  }
+  else if (!_countryCode.isEmpty())
+    location << _countryCode;
+
   return location.join(", ");
 }
 

Modified: vidalia/trunk/src/vidalia/network/GeoIp.h
===================================================================
--- vidalia/trunk/src/vidalia/network/GeoIp.h	2009-05-13 18:54:28 UTC (rev 3767)
+++ vidalia/trunk/src/vidalia/network/GeoIp.h	2009-05-13 19:07:26 UTC (rev 3768)
@@ -17,6 +17,7 @@
 #ifndef _GEOIP_H
 #define _GEOIP_H
 
+#include <QHash>
 #include <QString>
 #include <QHostAddress>
 
@@ -24,48 +25,71 @@
 class GeoIp
 {
 public:
-  /** Default constructor */
-  GeoIp() : _latitude(0xFFFF), _longitude(0xFFFF) {}
-  /** Constructor. */
-  GeoIp(QHostAddress ip);
+  /** Default constructor. Creates an empty GeoIp object.
+   */
+  GeoIp();
 
-  /** Constructor */
-  GeoIp(QHostAddress ip, float latitude, float longitude, 
-        QString city, QString state, QString country);
-  
-  /** Creates a GeoIp object from a string. */
-  static GeoIp fromString(QString geoip);
-  /** Builds a comma-delimited string of GeoIp fields. */
-  QString toString() const;
+  GeoIp(const QHostAddress &ip, float latitude, float longitude,
+        const QString &city = QString(),
+        const QString &region = QString(),
+        const QString &country = QString(),
+        const QString &countryCode = QString());
 
-  /** Returns the IP address for this object. */
+  /** Returns the IP address associated with this GeoIP object.
+   */
   QHostAddress ip() const { return _ip; }
-  /** Returns the latitude coordinate for this IP. */
+
+  /** Returns the latitude portion of the geographic coordinates associated
+   * with this IP address or range of IP addresses.
+   */
   float latitude() const { return _latitude; }
-  /** Returns the longitude coordinate for this IP. */
+
+  /** Returns the longitude portion of the geographic coordinates associated
+   * with this IP address or range of IP addresses.
+   */
   float longitude() const { return _longitude; }
-  /** Returns the city in which this IP lives. */
+
+  /** Returns the name of the city associated with this IP address, if known.
+   * Otherwise, returns an empty QString.
+   */
   QString city() const { return _city; }
-  /** Returns the state or district in which this IP lives. */
-  QString state() const { return _state; }
-  /** Returns the country in which this IP lives. */
+
+  /** Returns the full region name (e.g., state) in which this IP address 
+   * resides, if known. Otherwise, returns an empty QString.
+   */
+  QString region() const { return _region; }
+
+  /** Returns the full name of the country associated with this IP address
+   * or range of IP addresses, if known. Otherwise, returns an empty QString.
+   */
   QString country() const { return _country; }
-  /** Returns a human-readable string of city, region(state), and country. */
-  QString toLocation() const;
 
-  /** Returns true if the GeoIp object is invalid. */
-  bool isEmpty() const;
-  /** Returns true if the GeoIp object is valid, but no location information
-   * is known for the associated IP address. */
-   bool isUnknown() const;
+  /** Returns the ISO 3166-1 alpha-2 two-letter country code of the country
+   * associated with this IP address or range of IP addresses, if known.
+   * Otherwise, returns an empty QString.
+   */
+  QString countryCode() const { return _countryCode; }
 
+  /** Returns a human-readable string of city, region(state), and country.
+   * Some fields may be absent if they are not known. If no fields are known,
+   * this will return an empty QString.
+   */
+  QString toString() const;
+
+  /** Returns true if the GeoIp object is valid. A valid GeoIp object must
+   * have valid IP address, valid latitude and longitude coordinates and a 
+   * two-letter country code.
+   */
+  bool isValid() const;
+
 private:
   QHostAddress _ip; /**< IP address for this location. */
   float _latitude;  /**< Latitudinal coordinate for this IP's location. */
   float _longitude; /**< Longitudinal coordinate for this IP's location. */
   QString _city;    /**< City in which this IP lives. */
-  QString _state;   /**< State or district in which this IP lives. */
+  QString _region;   /**< State or district in which this IP lives. */
   QString _country; /**< Country in which this IP lives. */
+  QString _countryCode; /**< ISO-3166-1 alpha-2 country code. */
 };
 
 #endif

Modified: vidalia/trunk/src/vidalia/network/GeoIpCache.cpp
===================================================================
--- vidalia/trunk/src/vidalia/network/GeoIpCache.cpp	2009-05-13 18:54:28 UTC (rev 3767)
+++ vidalia/trunk/src/vidalia/network/GeoIpCache.cpp	2009-05-13 19:07:26 UTC (rev 3768)
@@ -15,6 +15,8 @@
 */
 
 #include "GeoIpCache.h"
+#include "GeoIpCacheItem.h"
+#include "GeoIp.h"
 #include "Vidalia.h"
 
 #include "file.h"
@@ -22,27 +24,24 @@
 
 #include <QFile>
 #include <QDir>
+#include <QString>
+#include <QDateTime>
 #include <QTextStream>
+#include <QHostAddress>
 
-/* Location of Vidalia's geoip cache file. Qt docs claims that QFile will
- * translate the "/" correctly on Windows. Hopefully they didn't lie. */
-#define CACHE_FILENAME  (Vidalia::dataDirectory() + "/geoip-cache")
 
-
-/** Constructor. */
-GeoIpCache::GeoIpCache()
+GeoIpCache::GeoIpCache(QObject *parent)
+  : QObject(parent)
 {
   loadFromDisk();
 }
 
-/** Returns the location currently used for the cache file. */
 QString
-GeoIpCache::cacheFilename()
+GeoIpCache::cacheFileName() const
 {
-  return CACHE_FILENAME;
+  return (Vidalia::dataDirectory() + "/geoip-cache");
 }
 
-/** Writes the current cache to disk. */
 bool
 GeoIpCache::saveToDisk(QString *errmsg)
 {
@@ -52,21 +51,21 @@
   }
   
   /* Try to open a temporary cache file for writing */
-  QFile tmpCacheFile(CACHE_FILENAME + ".tmp");
+  QFile tmpCacheFile(cacheFileName() + ".tmp");
   if (!tmpCacheFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
     return err(errmsg, tmpCacheFile.errorString());
   }
-  
+
   /* Write the cache entries to the file. */
   QTextStream cache(&tmpCacheFile);
-  foreach (GeoIpCacheItem cacheItem, _cache.values()) {
+  foreach (GeoIpCacheItem cacheItem, _cache) {
     /* Save the cache item if it's not too old. */
     if (!cacheItem.isExpired()) {
-      cache << cacheItem.toString() << endl;
+      cache << cacheItem.toCacheString() << endl;
     }
   }
-  
-  QFile cacheFile(CACHE_FILENAME);
+
+  QFile cacheFile(cacheFileName());
   /* Check if an previous cache file exists. */
   if (cacheFile.exists()) {
     /* A previous cache file exists, so try to remove it */
@@ -81,13 +80,11 @@
   return true;
 }
 
-/** Reads the cache contents in from disk. This function returns true if no
- * cache file exists, since it's possible nothing has been cached yet. */
 bool
 GeoIpCache::loadFromDisk(QString *errmsg)
 {
-  QFile cacheFile(CACHE_FILENAME);
-  
+  QFile cacheFile(cacheFileName());
+
   if (cacheFile.exists()) {
     /* Try to open the cache file */
     if (!cacheFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
@@ -97,49 +94,67 @@
     /* Read the cached items from the cache file */
     QTextStream cache(&cacheFile);
     QString line = cache.readLine();
-    while (!line.isNull()) {
+    while (! line.isNull()) {
       /* Create a GeoIpCacheItem from the line and save it */
-      GeoIpCacheItem item = GeoIpCacheItem::fromString(line);
-      if (!item.isEmpty() && !item.isExpired()) {
-        /* Only load non-stale cache items. */
-        _cache.insert(item.ip().toIPv4Address(), item);
-      }
+      GeoIpCacheItem item = GeoIpCacheItem::fromCacheString(line);
+      if (item.isValid() && ! item.isExpired())
+        addToCache(item);
+
       line = cache.readLine();
     }
+    vInfo("Parsed %1 GeoIP entries from '%2'").arg(_cache.size())
+                                              .arg(cacheFileName());
   }
   return true;
 }
 
-/** Caches the given IP and geographic information to disk. Call saveToDisk()
- * when you want to write the cache to disk. */
 void
-GeoIpCache::cache(GeoIp geoip)
+GeoIpCache::addToCache(const GeoIp &geoip)
 {
-  /* Store the entry in our in-memory cache */
-  _cache.insert(geoip.ip().toIPv4Address(), 
-                GeoIpCacheItem(geoip,QDateTime::currentDateTime()));
+  /* Create a "range" consisting of only a single IP address. */
+  if (! contains(geoip.ip()))
+    addToCache(geoip.ip(), geoip.ip(), geoip);
 }
 
-/** Returns a GeoIp object for the given IP from cache. */
+void
+GeoIpCache::addToCache(const QHostAddress &from, const QHostAddress &to,
+                       const GeoIp &geoip)
+{
+  /* New cache entries expire 30 days from the time they are first cached. */
+  QDateTime expires = QDateTime::currentDateTime().toUTC().addDays(30);
+  
+  /* Create a new GeoIpCacheItem and add it to the cache */
+  addToCache(GeoIpCacheItem(from, to, geoip, expires));
+}
+
+void
+GeoIpCache::addToCache(const GeoIpCacheItem &ci)
+{
+  if (! ci.isValid() || ci.isExpired())
+    return;
+
+  /* The key for the cache is the last IP address in the range of IP addresses
+   * covered by the GeoIpCacheItem. We do this because QMap::upperBound() and 
+   * QMap::lowerBound() return an iterator to the next item higher than the
+   * search value (an IP address) if an exact match is not found.
+   */
+  _cache.insert(ci.ipRangeEnd().toIPv4Address(), ci);
+}
+
 GeoIp
-GeoIpCache::geoip(QHostAddress ip)
+GeoIpCache::geoIpForAddress(const QHostAddress &ip)
 {
-  if (this->contains(ip)) {
-    return _cache.value(ip.toIPv4Address()).geoip();
-  }
+  GeoIpCacheMap::iterator i = _cache.upperBound(ip.toIPv4Address());
+  if (i != _cache.end() && (*i).contains(ip)) 
+    return (*i).toGeoIp(ip);
   return GeoIp();
 }
 
-/** Returns true if the given IP address is cached and the cached information
- * is not stale. */
 bool
-GeoIpCache::contains(QHostAddress ip)
+GeoIpCache::contains(const QHostAddress &ip)
 {
-  quint32 ipv4 = ip.toIPv4Address();
-  if (_cache.contains(ipv4)) {
-    GeoIpCacheItem cacheItem = _cache.value(ipv4);
-    return !cacheItem.isExpired();
-  }
-  return false;
+  GeoIpCacheMap::iterator i = _cache.upperBound(ip.toIPv4Address());
+  return (i != _cache.end() && (*i).contains(ip));
 }
 
+

Modified: vidalia/trunk/src/vidalia/network/GeoIpCache.h
===================================================================
--- vidalia/trunk/src/vidalia/network/GeoIpCache.h	2009-05-13 18:54:28 UTC (rev 3767)
+++ vidalia/trunk/src/vidalia/network/GeoIpCache.h	2009-05-13 19:07:26 UTC (rev 3768)
@@ -19,33 +19,68 @@
 
 #include "GeoIpCacheItem.h"
 
-#include <QString>
-#include <QHash>
-#include <QHostAddress>
+#include <QObject>
+#include <QMap>
 
+class GeoIp;
+class QString;
+class QHostAddress;
 
-class GeoIpCache
+typedef QMap<quint32, GeoIpCacheItem> GeoIpCacheMap;
+
+
+class GeoIpCache : public QObject
 {
+  Q_OBJECT
+
 public:
   /** Default constructor. */
-  GeoIpCache();
+  GeoIpCache(QObject *parent = 0);
   
-  /** Writes the current cache to disk. */
+  /** Writes the current cache to disk. Returns true if the cache file was
+   * successfully saved to disk. Otherwise, returns false and sets
+   * <b>errmsg</b> to a string describing the error encountered, if
+   * <b>errmsg</b> is not null.
+   */
   bool saveToDisk(QString *errmsg = 0);
-  /** Reads the cache in from disk. */
+  
+  /** Reads the cache in from disk. Returns true if the cache file was
+   * successfully read. Otherwise, returns false and sets <b>errmsg</b> to
+   * a string describing the error encountered, if <b>errmsg</b> is not null.
+   */
   bool loadFromDisk(QString *errmsg = 0);
   
-  /** Returns the location currently used for the cache file. */
-  QString cacheFilename();
-  /** Caches the given IP and geographic information to disk. */
-  void cache(GeoIp geoip);
-  /** Returns a GeoIp object for the given IP from cache. */
-  GeoIp geoip(QHostAddress ip);
-  /** Returns true if the given IP address is cached. */
-  bool contains(QHostAddress ip);
-  
+  /** Returns the location currently used for the cache file.
+   */
+  QString cacheFileName() const;
+
+  /** Caches the geographic information in <b>geoip</b> associated with a
+   * single IP address.
+   */
+  void addToCache(const GeoIp &geoip);
+
+  /** Caches the geographic information in <b>geoip</b> associated with a
+   * range of IP addresses, from <b>from</b> to <b>to</b> (inclusive).
+   */
+  void addToCache(const QHostAddress &from, const QHostAddress &to,
+                  const GeoIp &geoip);
+
+  /** Returns a GeoIp object for the given <b>ip</b> from cache. If no cached
+   * information is known for <b>ip</b>, an empty GeoIp object is returned.
+   */
+  GeoIp geoIpForAddress(const QHostAddress &ip);
+
+  /** Returns true if the cache contains geographic location information for
+   * <b>ip</b>. Otherwise, returns false.
+   */
+  bool contains(const QHostAddress &ip);
+
 private:
-  QHash<quint32, GeoIpCacheItem> _cache;  /**< List of cached GeoIp objects. */  
+  /** Adds the GeoIpCacheItem <b>ci</b> to the cache. */
+  void addToCache(const GeoIpCacheItem &ci);
+
+  /**< List of cached GeoIp objects. */
+  GeoIpCacheMap _cache;  
 };
 
 #endif

Modified: vidalia/trunk/src/vidalia/network/GeoIpCacheItem.cpp
===================================================================
--- vidalia/trunk/src/vidalia/network/GeoIpCacheItem.cpp	2009-05-13 18:54:28 UTC (rev 3767)
+++ vidalia/trunk/src/vidalia/network/GeoIpCacheItem.cpp	2009-05-13 19:07:26 UTC (rev 3768)
@@ -15,70 +15,169 @@
 */
 
 #include "GeoIpCacheItem.h"
+#include "GeoIp.h"
 
+#include "stringutil.h"
+
+#include <QString>
+#include <QDateTime>
 #include <QStringList>
 
+#define CACHE_KEY_FROM_IP       "FROM"
+#define CACHE_KEY_TO_IP         "TO"
+#define CACHE_KEY_EXPIRES       "EXPIRES"
+#define CACHE_KEY_LATITUDE      "LAT"
+#define CACHE_KEY_LONGITUDE     "LON"
+#define CACHE_KEY_CITY          "CITY"
+#define CACHE_KEY_REGION        "REGION"
+#define CACHE_KEY_COUNTRY       "COUNTRY"
+#define CACHE_KEY_COUNTRY_CODE  "CC"
 
-/** Constructor */
-GeoIpCacheItem::GeoIpCacheItem(GeoIp geoip, QDateTime timestamp)
+
+GeoIpCacheItem::GeoIpCacheItem()
 {
-  _geoip = geoip;
-  _timestamp = timestamp;
+  _fromIp = 0;
+  _toIp = 0;
 }
 
-/** Returns true if this cache item is empty and invalid. A valid cache item
- * must have a valid GeoIp object and timestamp. */
-bool
-GeoIpCacheItem::isEmpty() const
+GeoIpCacheItem::GeoIpCacheItem(const QHostAddress &from, const QHostAddress &to,
+                               const GeoIp &geoip, const QDateTime &expires)
 {
-  return (_geoip.isEmpty() || _timestamp.isNull());
+  _fromIp = from.toIPv4Address();
+  _toIp = to.toIPv4Address();
+  _expires = expires;
+
+  _fields.insert(CACHE_KEY_LATITUDE, geoip.latitude());
+  _fields.insert(CACHE_KEY_LONGITUDE, geoip.longitude());
+  if (! geoip.city().isEmpty())
+    _fields.insert(CACHE_KEY_CITY, geoip.city());
+  if (! geoip.region().isEmpty())
+    _fields.insert(CACHE_KEY_REGION, geoip.region());
+  if (! geoip.country().isEmpty())
+    _fields.insert(CACHE_KEY_COUNTRY, geoip.country());
+  if (! geoip.countryCode().isEmpty())
+    _fields.insert(CACHE_KEY_COUNTRY_CODE, geoip.countryCode());
 }
 
-/** Returns a string representing the contents of this cache item, suitable
- * for writing to disk. The format is as in the following example:
- *                     <Geo IP Data>:<Timestamp>
- */
-QString
-GeoIpCacheItem::toString() const
+QHostAddress
+GeoIpCacheItem::ipRangeStart() const
 {
-  return _geoip.toString() + ":" + QString::number(_timestamp.toTime_t());
+  return QHostAddress(_fromIp);
 }
 
-/** Returns a GeoIpCacheItem from a string as read from the cache that was
- * written to disk. The format is:
- *                     <Geo IP Data>[:<Timestamp>]
- *
- * If no value for Timestamp is given, the current date and time will be used.
- * If the string cannot be parsed for valid cached GeoIP data, then an empty
- * GeoIpCacheItem object is returned. The calling method should call isEmpty()
- * on the returned GeoIpCacheItem object to ensure it got a valid object.
- */
-GeoIpCacheItem
-GeoIpCacheItem::fromString(QString cacheString)
+QHostAddress
+GeoIpCacheItem::ipRangeEnd() const
 {
-  QDateTime timestamp;
-  QStringList cacheData = cacheString.split(":");
+  return QHostAddress(_toIp);
+}
 
-  if (cacheData.size() >= 1) {
-    GeoIp geoip = GeoIp::fromString(cacheData.at(0));
-    if (cacheData.size() >= 2)
-      timestamp.setTime_t(cacheData.at(1).toUInt());
-    else
-      timestamp = QDateTime::currentDateTime();
-    return GeoIpCacheItem(geoip, timestamp);
-  }
-  return GeoIpCacheItem();
+bool
+GeoIpCacheItem::contains(const QHostAddress &ip) const
+{
+  quint32 ipv4 = ip.toIPv4Address();
+
+  return (ipv4 >= _fromIp && ipv4 <= _toIp);
 }
 
-/** Returns true if the cache item is too old to be considered valid. Normal
- * cached responses are valid for one month. Cached UNKNOWN responses are
- * considered valid for one week. */
 bool
+GeoIpCacheItem::isValid() const
+{
+  return (_expires.isValid()
+            && ! QHostAddress(_fromIp).isNull()
+            && ! QHostAddress(_toIp).isNull()
+            && _fromIp <= _toIp
+            && _fields.contains(CACHE_KEY_LATITUDE)
+            && _fields.contains(CACHE_KEY_LONGITUDE));
+}
+
+bool
 GeoIpCacheItem::isExpired() const
 {
-  if (_geoip.isUnknown()) {
-    return (_timestamp.addDays(7) < QDateTime::currentDateTime());
+  return (_expires < QDateTime::currentDateTime().toUTC());
+}
+
+GeoIp
+GeoIpCacheItem::toGeoIp(const QHostAddress &ip) const
+{
+  if (this->contains(ip))
+    return GeoIp(ip,
+                 _fields.value(CACHE_KEY_LATITUDE).toDouble(),
+                 _fields.value(CACHE_KEY_LONGITUDE).toDouble(),
+                 _fields.value(CACHE_KEY_CITY).toString(),
+                 _fields.value(CACHE_KEY_REGION).toString(),
+                 _fields.value(CACHE_KEY_COUNTRY).toString(),
+                 _fields.value(CACHE_KEY_COUNTRY_CODE).toString());
+  return GeoIp();
+}
+
+QString
+GeoIpCacheItem::toCacheString() const
+{
+  QStringList keyvals;
+
+  keyvals << QString(CACHE_KEY_FROM_IP"=%1").arg(QHostAddress(_fromIp).toString());
+  keyvals << QString(CACHE_KEY_TO_IP"=%1").arg(QHostAddress(_toIp).toString());
+  keyvals << QString(CACHE_KEY_EXPIRES"=\"%1\"").arg(_expires.toString(Qt::ISODate));
+
+  foreach (QString key, _fields.keys()) {
+    QString value = _fields.value(key).toString();
+    if (value.contains(" ")) {
+      value.replace("\\", "\\\\");
+      value.replace("\"", "\\\"");
+      value = "\"" + value + "\"";
+    }
+    keyvals << key + "=" + value;
   }
-  return (_timestamp.addMonths(1) < QDateTime::currentDateTime()); 
+  return keyvals.join(" ");
 }
 
+GeoIpCacheItem
+GeoIpCacheItem::fromCacheString(const QString &line)
+{
+  GeoIpCacheItem ci;
+  bool ok;
+
+  QHash<QString,QString> keyvals = string_parse_keyvals(line, &ok);
+  if (! ok)
+    return GeoIpCacheItem();
+  
+  /* Get the range of IP addresses associated with this cache entry */
+  QHostAddress fromIp(keyvals.value(CACHE_KEY_FROM_IP));
+  QHostAddress toIp(keyvals.value(CACHE_KEY_TO_IP));
+  if (fromIp.isNull() || toIp.isNull())
+    return GeoIpCacheItem();
+  ci._fromIp = fromIp.toIPv4Address();
+  ci._toIp = toIp.toIPv4Address();
+
+  /* Extract the expiration timestamp of this entry */
+  ci._expires = QDateTime::fromString(keyvals.value(CACHE_KEY_EXPIRES),
+                                   Qt::ISODate);
+  if (! ci._expires.isValid())
+    ci._expires = QDateTime::currentDateTime().toUTC().addDays(30);
+  
+  
+  /* Make sure we have valid geographic coordinates */
+  float latitude = keyvals.value(CACHE_KEY_LATITUDE).toFloat(&ok);
+  if (! ok)
+    return GeoIpCacheItem();
+  ci._fields.insert(CACHE_KEY_LATITUDE, latitude);
+
+  float longitude = keyvals.value(CACHE_KEY_LONGITUDE).toFloat(&ok);
+  if (! ok)
+    return GeoIpCacheItem();
+  ci._fields.insert(CACHE_KEY_LONGITUDE, longitude);
+  
+  /* Each of these fields is optional */
+  if (keyvals.contains(CACHE_KEY_CITY))
+    ci._fields.insert(CACHE_KEY_CITY, keyvals.value(CACHE_KEY_CITY));
+  if (keyvals.contains(CACHE_KEY_REGION))
+    ci._fields.insert(CACHE_KEY_REGION, keyvals.value(CACHE_KEY_REGION));
+  if (keyvals.contains(CACHE_KEY_COUNTRY))
+    ci._fields.insert(CACHE_KEY_COUNTRY, keyvals.value(CACHE_KEY_COUNTRY));
+  if (keyvals.contains(CACHE_KEY_COUNTRY_CODE))
+    ci._fields.insert(CACHE_KEY_COUNTRY_CODE,
+                      keyvals.value(CACHE_KEY_COUNTRY_CODE));
+
+  return ci;
+}
+

Modified: vidalia/trunk/src/vidalia/network/GeoIpCacheItem.h
===================================================================
--- vidalia/trunk/src/vidalia/network/GeoIpCacheItem.h	2009-05-13 18:54:28 UTC (rev 3767)
+++ vidalia/trunk/src/vidalia/network/GeoIpCacheItem.h	2009-05-13 19:07:26 UTC (rev 3768)
@@ -17,38 +17,80 @@
 #ifndef _GEOIPCACHEITEM_H
 #define _GEOIPCACHEITEM_H
 
-#include "GeoIp.h"
-
+#include <QHash>
+#include <QString>
+#include <QVariant>
 #include <QDateTime>
 
+class GeoIp;
+class QHostAddress;
 
+
 class GeoIpCacheItem
 {
 public:
-  /** Default constructor */
-  GeoIpCacheItem() {};
-  /** Constructor. */
-  GeoIpCacheItem(GeoIp geoip, QDateTime timestamp);
+  /** Default constructor
+   */
+  GeoIpCacheItem();
 
-  /** Returns the IP of this cache item. */
-  QHostAddress ip() const { return _geoip.ip(); }
-  /** Returns the cached GeoIp object. */
-  GeoIp geoip() const { return _geoip; }
-  /** Returns true if this cache item is expired. */
+  /** Constructor.
+   */
+  GeoIpCacheItem(const QHostAddress &from, const QHostAddress &to,
+                 const GeoIp &geoIp, const QDateTime &expires);
+
+  /** Returns the first IP address in the range of IP addresses associated
+   * with this GeoIpCacheItem.
+   */
+  QHostAddress ipRangeStart() const;
+
+  /** Returns the last IP address in the range of IP addresses associated
+   * with this GeoIpCacheItem.
+   */
+  QHostAddress ipRangeEnd() const;
+
+  /** Returns true if <b>ip</b> is within the range of IP addresses associated
+   * with this GeoIpCacheItem.
+   * \sa ipRangeStart()
+   * \sa ipRangeEnd()
+   */
+  bool contains(const QHostAddress &ip) const;
+
+  /** Returns true if this GeoIpCacheItem object contains valid values for
+   * all required fields.
+   */
+  bool isValid() const;
+
+  /** Returns true if the cache item is too old to be considered accurate.
+   * Cached GeoIp responses are considered valid for thirty days after they
+   * are first added to the cache.
+   */
   bool isExpired() const;
-  /** Returns true if this cache item is empty and invalid. */
-  bool isEmpty() const;
 
-  /** Returns a string representing the contents of this cache item, suitable
-   * for writing to disk. */
-  QString toString() const;
-  /** Returns a GeoIpCacheItem from a string as read from the cache that was
-   * written to disk. */
-  static GeoIpCacheItem fromString(QString cacheString);
+  /** Returns a GeoIp object for <b>ip</b>, populated with the cached
+   * geographic information stored by this GeoIpCacheObject. If <b>ip</b>
+   * is not within the range of IP addresses associated with this object,
+   * an empty GeoIp object is returned.
+   * \sa contains
+   */
+  GeoIp toGeoIp(const QHostAddress &ip) const;
 
+  /** Formats the fields contained in this GeoIpCacheItem as a string
+   * suitable for writing to a cache file.
+   */
+  QString toCacheString() const;
+  
+  /** Parses <b>cacheLine</b> and constructs a new GeoIpCacheItem object
+   * with the parsed values. The format of <b>cacheLine</b> must follow the
+   * format as produced by toCacheString().
+   * \sa toCacheString()
+   */
+  static GeoIpCacheItem fromCacheString(const QString &cacheLine);
+
 private:
-  GeoIp     _geoip;      /**< Cached GeoIp item. */
-  QDateTime _timestamp;  /**< Time this item was cached. */
+  quint32 _fromIp;
+  quint32 _toIp;
+  QDateTime _expires;  /**< Time this item was cached. */
+  QHash<QString,QVariant> _fields;
 };
 
 #endif

Modified: vidalia/trunk/src/vidalia/network/GeoIpRequest.cpp
===================================================================
--- vidalia/trunk/src/vidalia/network/GeoIpRequest.cpp	2009-05-13 18:54:28 UTC (rev 3767)
+++ vidalia/trunk/src/vidalia/network/GeoIpRequest.cpp	2009-05-13 19:07:26 UTC (rev 3768)
@@ -17,7 +17,11 @@
 #include "GeoIpRequest.h"
 #include "ZlibByteArray.h"
 
+#include <QString>
+#include <QHostAddress>
+#include <QHttpRequestHeader>
 
+
 /** Creates an HTTP POST header for this request, based on the 
  * Host, Page, and content-length values. */
 QHttpRequestHeader
@@ -45,7 +49,7 @@
 void
 GeoIpRequest::setRequest(const QList<QHostAddress> &ips)
 {
-  _request = "ip=";
+  _request = "format=long&ip=";
   int ipcount = ips.size();
 
   /* Add each IP to a comma-delimited list. */

Modified: vidalia/trunk/src/vidalia/network/GeoIpRequest.h
===================================================================
--- vidalia/trunk/src/vidalia/network/GeoIpRequest.h	2009-05-13 18:54:28 UTC (rev 3767)
+++ vidalia/trunk/src/vidalia/network/GeoIpRequest.h	2009-05-13 19:07:26 UTC (rev 3768)
@@ -19,11 +19,12 @@
 
 #include <QList>
 #include <QString>
-#include <QByteArray>
-#include <QHostAddress>
 #include <QHttpRequestHeader>
 
+class QHostAddress;
+class QByteArray;
 
+
 class GeoIpRequest
 {
 public:

Modified: vidalia/trunk/src/vidalia/network/GeoIpResolver.cpp
===================================================================
--- vidalia/trunk/src/vidalia/network/GeoIpResolver.cpp	2009-05-13 18:54:28 UTC (rev 3767)
+++ vidalia/trunk/src/vidalia/network/GeoIpResolver.cpp	2009-05-13 19:07:26 UTC (rev 3768)
@@ -15,29 +15,35 @@
 */
 
 #include "GeoIpResolver.h"
+#include "GeoIpRequest.h"
+#include "GeoIpResponse.h"
+#include "GeoIp.h"
 #include "Vidalia.h"
 
+#include "stringutil.h"
 #include "TorSslSocket.h"
 
-/** Host for the geo ip information. */ 
-#define GEOIP_HOST    "geoip.vidalia-project.net"
-/** The SSL GeoIP service runs on port 1443 (443 was taken). */
-#define GEOIP_SSL_PORT  1443
-/** Page that we request the geo ip information from. */
-#define GEOIP_PAGE    "/cgi-bin/geoip"
+/** Host for the GeoIP information. */ 
+#define GEOIP_HOST    "geoips.vidalia-project.net"
+/** The SSL GeoIP service runs on port 443. */
+#define GEOIP_SSL_PORT  443
+/** Page that we request the GeoIP information from. */
+#define GEOIP_PAGE    "/cgi-bin/geoip.py"
 
 
 /** Default constructor. */
-GeoIpResolver::GeoIpResolver()
+GeoIpResolver::GeoIpResolver(QObject *parent)
+  : QObject(parent)
 {
   _socksAddr = QHostAddress::LocalHost;
   _socksPort = 9050;
+  _cache = new GeoIpCache(this);
 }
 
 /** Sets the address and port of Tor, through which GeoIP requests will be
  * made. */
 void
-GeoIpResolver::setSocksHost(QHostAddress addr, quint16 port)
+GeoIpResolver::setSocksHost(const QHostAddress &addr, quint16 port)
 {
   _socksAddr = addr;
   _socksPort = port;
@@ -47,10 +53,10 @@
  * signal will be emitted and true returned if we have cached geographic
  * information for <b>ip</b>. Otherwise, this returns false. */
 bool
-GeoIpResolver::resolveFromCache(QHostAddress ip)
+GeoIpResolver::resolveFromCache(const QHostAddress &ip)
 {
-  if (_cache.contains(ip)) {
-    emit resolved(-1, QList<GeoIp>() << _cache.geoip(ip));
+  if (_cache->contains(ip)) {
+    emit resolved(-1, QList<GeoIp>() << _cache->geoIpForAddress(ip));
     return true;
   }
   return false;
@@ -59,21 +65,19 @@
 /** Resolves a list of IPs to a geographic location, but only those which are
  * cached. Returns a list of IPs that were not in the cache. */
 QList<QHostAddress>
-GeoIpResolver::resolveFromCache(QList<QHostAddress> ips)
+GeoIpResolver::resolveFromCache(const QList<QHostAddress> &ips)
 {
   QList<GeoIp> cached;
 
   /* Build a list of which IPs have cached GeoIp information */
   foreach (QHostAddress ip, ips) {
-    if (_cache.contains(ip)) {
-      ips.removeAt(ips.indexOf(ip));
-      cached << _cache.geoip(ip);
-    }
+    if (_cache->contains(ip))
+      cached << _cache->geoIpForAddress(ip);
   }
 
   /* If any were cached, emit their results now */
   if (cached.size() > 0) {
-    vInfo("Resolved %1 GeoIP entries from cache.").arg(ips.size());
+    vInfo("Resolved %1 GeoIP entries from cache.").arg(cached.size());
     emit resolved(-1, cached);
   }
   return ips;
@@ -81,7 +85,7 @@
 
 /** Resolves a single IP to a geographic location. */
 int
-GeoIpResolver::resolve(QHostAddress ip)
+GeoIpResolver::resolve(const QHostAddress &ip)
 {
   return resolve(QList<QHostAddress>() << ip);
 }
@@ -95,7 +99,8 @@
   if (!_requestList.contains(socket)) {
     return;
   }
-  GeoIpRequest *req = (GeoIpRequest *)_requestList.value(socket);
+  GeoIpRequest *req = static_cast<GeoIpRequest *>(_requestList.value(socket));
+
   vInfo("Connected to the GeoIP host. Sending request for %1 uncached "
         "GeoIP entries. (request id %2)").arg(req->size()).arg(req->id());
 
@@ -112,63 +117,107 @@
   if (!_requestList.contains(socket)) {
     return;
   }
-  GeoIpRequest *request = (GeoIpRequest *)_requestList.take(socket);
+  GeoIpRequest *req = static_cast<GeoIpRequest *>(_requestList.take(socket));
 
-  /* Read and parse the response */
+  /* Read and parse the response header */
   GeoIpResponse response = GeoIpResponse(socket->readAll());
 
   /* Check the response code and see what we got */
   if (response.statusCode() == 200) {
-    /* We got a 200 OK, so get the Geo IP information and cache the results */
-    int numCached = 0, i = 0;
-    QList<GeoIp> geoips = response.geoIps();
-    foreach (GeoIp geoip, geoips) {
-      QHostAddress ip = geoip.ip();
-      
-      if (request->contains(ip)) {
-        /* This is a requested geoip item, so if it wasn't cached, then 
-         * cache it now. */
-        if (!_cache.contains(ip)) {
-          _cache.cache(geoip);
-          numCached++;
-        }
-        i++;
-      } else {
-        /* This item wasn't requested, so remove it. According to the Qt docs,
-         * this is safe to do inside the foreach() loop because, "Qt
-         * automatically takes a copy of the container when it enters a
-         * foreach loop. If you modify the container as you are iterating,
-         * that won't affect the loop." */
-        vWarn("Received a GeoIP entry for IP address %1 that was not included "
-              "in the initial request. (request id %2)").arg(ip)
-                                                        .arg(request->id());
-        geoips.removeAt(i);
-      }
-    }
-    /* If new results were cached, save them to disk */
-    if (numCached > 0) {
-      _cache.saveToDisk();
-    }
-    /* Emit the results */
-    vInfo("Parsed %1 entries from the GeoIP response. (request id %2)")
-                                 .arg(geoips.size()).arg(request->id());
-    emit resolved(request->id(), geoips);
+    /* We got a 200 OK, so get the Geo IP information from the response body
+     * and cache the results. */
+    parseGeoIpResponse(response.content(), req);
   } else {
     /* We failed to get the Geo IP information, so emit resolveFailed and
      * include the HTTP status message. */
-    vWarn("GeoIP resolution failed (request id %1): %2").arg(request->id())
+    vWarn("GeoIP resolution failed (request id %1): %2").arg(req->id())
                                              .arg(response.statusMessage());
-    emit resolveFailed(request->id(), response.statusMessage());
+    emit resolveFailed(req->id(), response.statusMessage());
   }
   /* Close the socket and clean up */
   socket->close();
   delete socket;
-  delete request;
+  delete req;
 }
 
+void
+GeoIpResolver::parseGeoIpResponse(const QByteArray &response,
+                                  GeoIpRequest *request)
+{
+  QList<GeoIp> geoIpList;
+  QHash<QString,QString> keyvals;
+  QHostAddress ip, from, to;
+  QString city, region, country, cc;
+  float latitude, longitude;
+  GeoIp geoIp;
+  int numCached = 0;
+  bool ok;
+
+  QStringList lines = QString(response).split("\n", QString::SkipEmptyParts);
+  foreach (QString line, lines) {
+    /* Split the key=value formatted GeoIP record into keys and values */
+    QHash<QString,QString> keyvals = string_parse_keyvals(line.trimmed(), &ok);
+    if (! ok)
+      goto err;
+
+    /* Extract each of the required fields from the GeoIP record */
+    ip = QHostAddress(keyvals.value("IP"));
+    if (ip.isNull())
+      goto err;
+    latitude = keyvals.value("LAT").toFloat(&ok);
+    if (! ok)
+      goto err;
+    longitude = keyvals.value("LON").toFloat(&ok);
+    if (! ok)
+      goto err;
+
+    /* Each of these fields is optional */
+    city    = keyvals.value("CITY");
+    region  = keyvals.value("REGION");
+    country = keyvals.value("COUNTRY");
+    cc      = keyvals.value("CC");
+    
+    geoIp = GeoIp(ip, latitude, longitude, city, region, country, cc);
+    if (! geoIp.isValid())
+      goto err;
+
+    if (request->contains(ip)) {
+      if (! _cache->contains(ip)) {
+        from = QHostAddress(keyvals.value("FROM"));
+        to   = QHostAddress(keyvals.value("TO"));
+        if (! from.isNull() && ! to.isNull())
+          _cache->addToCache(from, to, geoIp);
+        else
+          _cache->addToCache(geoIp);
+        numCached++;
+      }
+
+      geoIpList << geoIp;
+      continue;
+
+err:
+      vInfo("Ignored improperly formatted GeoIP record (request id %1): %2")
+                                               .arg(line).arg(request->id());
+    } else {
+      /* This item wasn't requested, so just log it and ignore. */
+      vWarn("Received a GeoIP entry for IP address %1 that was not included "
+            "in the initial request. (request id %2)").arg(ip)
+                                                      .arg(request->id());
+    }
+  }
+  /* If new results were cached, save them to disk */
+  if (numCached > 0)
+    _cache->saveToDisk();
+  
+  /* Emit the results */
+  vInfo("Parsed %1 entries from the GeoIP response. (request id %2)")
+                                   .arg(geoIpList.size()).arg(request->id());
+  emit resolved(request->id(), geoIpList);  
+}
+
 /** Called when an error has occurred requesting Geo IP information. */
 void
-GeoIpResolver::socketError(QString errorString)
+GeoIpResolver::socketError(const QString &errorString)
 {
   /* Find the socket and request for whoever called this slot */ 
   QAbstractSocket *socket = dynamic_cast<QAbstractSocket *>(sender());
@@ -177,22 +226,22 @@
   }
   
   /* We expect a remote host to close the socket, because that's how the HTTP
-   * server tells us he's done talkig to us. */
+   * server tells us he's done talking to us. */
   if (socket->error() != QAbstractSocket::RemoteHostClosedError) {
     /* Emit the failure and clean up */
-    GeoIpRequest *request = (GeoIpRequest *)_requestList.take(socket);
-    emit resolveFailed(request->id(), errorString);
+    GeoIpRequest *req = static_cast<GeoIpRequest *>(_requestList.take(socket));
+    emit resolveFailed(req->id(), errorString);
     socket->abort();
-    vWarn("GeoIP request socket error (request id %1): %2").arg(request->id())
+    vWarn("GeoIP request socket error (request id %1): %2").arg(req->id())
                                                            .arg(errorString);
     delete socket;
-    delete request;
+    delete req;
   }
 }
 
 /** Creates an HTTP request for Geo IP information. */
 GeoIpRequest*
-GeoIpResolver::createRequest(QList<QHostAddress> ips)
+GeoIpResolver::createRequest(const QList<QHostAddress> &ips)
 {
   static int id = -1;
   GeoIpRequest *request = new GeoIpRequest(++id);
@@ -204,18 +253,16 @@
 
 /** Resolves a list of IPs to a geographic location. */
 int
-GeoIpResolver::resolve(QList<QHostAddress> ips)
+GeoIpResolver::resolve(const QList<QHostAddress> &ips)
 {
   /* Resolve the cached IPs and get a list of IPs that still need to be
    * resolved to a lat and long. */
   QList<QHostAddress> uncached = resolveFromCache(ips);
-  if (!uncached.size()) {
+  if (! uncached.size())
     return -1;
-  }
 
   /* Create a socket used to request the geo ip information. */
   TorSslSocket *socket = new TorSslSocket(_socksAddr, _socksPort);
-
   connect(socket, SIGNAL(connectedToRemoteHost()), this, SLOT(connected()),
           Qt::QueuedConnection);
   connect(socket, SIGNAL(socketError(QString)), 
@@ -223,7 +270,7 @@
           Qt::QueuedConnection);
   connect(socket, SIGNAL(disconnected()), this, SLOT(disconnected()),
           Qt::QueuedConnection);
-  GeoIpRequest *request = createRequest(ips);
+  GeoIpRequest *request = createRequest(uncached);
   _requestList.insert(socket, request);
   
   /* Connect so we can send our request and return the request ID. */

Modified: vidalia/trunk/src/vidalia/network/GeoIpResolver.h
===================================================================
--- vidalia/trunk/src/vidalia/network/GeoIpResolver.h	2009-05-13 18:54:28 UTC (rev 3767)
+++ vidalia/trunk/src/vidalia/network/GeoIpResolver.h	2009-05-13 19:07:26 UTC (rev 3768)
@@ -17,44 +17,46 @@
 #ifndef _GEOIPRESOLVER_H
 #define _GEOIPRESOLVER_H
 
-#include "GeoIp.h"
 #include "GeoIpCache.h"
-#include "GeoIpRequest.h"
-#include "GeoIpResponse.h"
 
 #include <QObject>
 #include <QList>
 #include <QHash>
-#include <QString>
 #include <QHostAddress>
 
+class GeoIp;
+class GeoIpRequest;
+class GeoIpResponse;
+class QString;
+class QAbstractSocket;
 
+
 class GeoIpResolver : public QObject
 {
   Q_OBJECT
 
 public:
   /** Default constructor. */
-  GeoIpResolver();
+  GeoIpResolver(QObject *parent = 0);
   
   /** Sets the address and port of Tor, through which GeoIP requests will be
    * made. */
-  void setSocksHost(QHostAddress addr, quint16 port);
+  void setSocksHost(const QHostAddress &addr, quint16 port);
   /** Resolves a single IP to a geographic location. */
-  int resolve(QHostAddress ip);
+  int resolve(const QHostAddress &ip);
   /** Resolves a list of IPs to a geographic location. */
-  int resolve(QList<QHostAddress> ips);
+  int resolve(const QList<QHostAddress> &ips);
   /** Resolves <b>ip</b> to geographic information only if it is cached. */
-  bool resolveFromCache(QHostAddress ip);
+  bool resolveFromCache(const QHostAddress &ip);
   /** Resolves a list of IPs to a geographic location, but only those which
    * are cached. Returns a list of which IPs were not cached. */
-  QList<QHostAddress> resolveFromCache(QList<QHostAddress> ips);
+  QList<QHostAddress> resolveFromCache(const QList<QHostAddress> &ips);
 
 signals:
   /** Emitted when a list of IPs have been resolved to lat/long. */
-  void resolved(int id, QList<GeoIp> geoips);
+  void resolved(int id, const QList<GeoIp> &geoips);
   /** Emitted when a resolve has failed. */
-  void resolveFailed(int id, QString errorString);
+  void resolveFailed(int id, const QString &errorString);
 
 private slots:
   /** Called when the socket has connected to the Geo IP host. */
@@ -62,14 +64,16 @@
   /** Called when the socket has disconnected from the Geo IP host. */
   void disconnected();
   /** Called when an error has occurred getting the Geo IP information. */
-  void socketError(QString errorString);
+  void socketError(const QString &errorString);
 
 private:
   /** Creates an HTTP request for Geo IP information. */
-  GeoIpRequest* createRequest(QList<QHostAddress> ips);
+  GeoIpRequest* createRequest(const QList<QHostAddress> &ips);
 
+  void parseGeoIpResponse(const QByteArray &response, GeoIpRequest *request);
+
   /**< Cached GeoIp objects. */
-  GeoIpCache  _cache;
+  GeoIpCache*  _cache;
   /**< List of sockets used for requests. */
   QHash<QAbstractSocket *,GeoIpRequest*> _requestList;
   /** Tor's SocksListenAddress. */

Modified: vidalia/trunk/src/vidalia/network/GeoIpResponse.cpp
===================================================================
--- vidalia/trunk/src/vidalia/network/GeoIpResponse.cpp	2009-05-13 18:54:28 UTC (rev 3767)
+++ vidalia/trunk/src/vidalia/network/GeoIpResponse.cpp	2009-05-13 19:07:26 UTC (rev 3768)
@@ -14,10 +14,15 @@
 ** \brief Parses a response to a previous GeoIP request
 */
 
-#include <GeoIpResponse.h>
-#include <ZlibByteArray.h>
+#include "GeoIpResponse.h"
+#include "GeoIp.h"
+#include "Vidalia.h"
 
+#include "ZlibByteArray.h"
+
+#include <QByteArray>
 #include <QStringList>
+#include <QHttpResponseHeader>
 
 /** Status code for a successful HTTP request. */
 #define STATUS_HTTP_OK                 200
@@ -27,9 +32,7 @@
 #define STATUS_TRANSFER_ENCODING_ERR   602
 
 
-/** Constructor. Parses the response data for an HTTP header and Geo IP
- * information. */
-GeoIpResponse::GeoIpResponse(QByteArray response)
+GeoIpResponse::GeoIpResponse(const QByteArray &response)
 {
   QString errmsg;
   
@@ -39,13 +42,13 @@
 
   /* Parse out the Geo IP information, if any was included. */
   if (headerPos > 0 && _header.statusCode() == STATUS_HTTP_OK) {
-    QByteArray content = response.mid(headerPos+4);
+    _content = response.mid(headerPos+4);
  
     if (_header.hasKey("Transfer-Encoding")) {
       QString encoding = _header.value("Transfer-Encoding");
       if (encoding == "chunked") {
-        content = decodeChunked(content);
-        if (content.isEmpty()) {
+        _content = decodeChunked(_content);
+        if (_content.isEmpty()) {
           _header.setStatusLine(STATUS_TRANSFER_ENCODING_ERR,
             QString("Failed to decode chunked response"));
           return;
@@ -72,29 +75,19 @@
         return;
       }
  
-      content = ZlibByteArray::uncompress(content, method, &errmsg);
-      if (content.isEmpty()) {
+      _content = ZlibByteArray::uncompress(_content, method, &errmsg);
+      if (_content.isEmpty()) {
         _header.setStatusLine(STATUS_CONTENT_ENCODING_ERR,
           QString("Content decoding using method '%1' failed: %2")
                                        .arg(encoding).arg(errmsg));
         return;
       }
     }
-
-    /* Parse the Geo IP information in each line */
-    QStringList lines = QString(content).split("\n");
-    foreach (QString line, lines) {
-      GeoIp geoip = GeoIp::fromString(line);
-      if (!geoip.isEmpty())
-        _geoips << geoip;
-    }
   }
 }
 
-/** Decodes a <b>chunked</b> transfer encoding. Returns the unchunked 
- * result on success, or an empty QByteArray if decoding fails. */
 QByteArray
-GeoIpResponse::decodeChunked(QByteArray chunked)
+GeoIpResponse::decodeChunked(const QByteArray &chunked)
 {
   QByteArray unchunked;
   QString sizeString;
@@ -124,3 +117,21 @@
   return unchunked;
 }
 
+int
+GeoIpResponse::statusCode() const
+{
+  return _header.statusCode();
+}
+
+QString
+GeoIpResponse::statusMessage() const
+{
+  return _header.reasonPhrase();
+}
+
+QByteArray
+GeoIpResponse::content() const
+{
+  return _content;
+}
+

Modified: vidalia/trunk/src/vidalia/network/GeoIpResponse.h
===================================================================
--- vidalia/trunk/src/vidalia/network/GeoIpResponse.h	2009-05-13 18:54:28 UTC (rev 3767)
+++ vidalia/trunk/src/vidalia/network/GeoIpResponse.h	2009-05-13 19:07:26 UTC (rev 3768)
@@ -17,34 +17,41 @@
 #ifndef _GEOIPRESPONSE_H
 #define _GEOIPRESPONSE_H
 
-#include <GeoIp.h>
-
 #include <QList>
 #include <QByteArray>
 #include <QHttpResponseHeader>
 
+class GeoIp;
+class QString;
+class QStringList;
 
+
 class GeoIpResponse
 {
 public:
   /** Constructor. Parses the response data for an HTTP header and Geo IP
    * information.  */
-  GeoIpResponse(QByteArray response);
+  GeoIpResponse(const QByteArray &response);
 
-  /** Returns the HTTP status code for this response. */
-  int statusCode() { return _header.statusCode(); }
-  /** Returns the HTTP status message for this response. */
-  QString statusMessage() { return _header.reasonPhrase(); }
-  /** Returns the Geo IP information contained in this response. */
-  QList<GeoIp> geoIps() { return _geoips; }
+  /** Returns the HTTP status code for this response.
+   */
+  int statusCode() const;
+
+  /** Returns the HTTP status message for this response.
+   */
+  QString statusMessage() const;
+
+  /** Returns the Geo IP information contained in this response.
+   */
+  QByteArray content() const;
   
 private:
   /** Decodes a <b>chunked</b> transfer encoding. Returns the unchunked 
    * result on success, or an empty QByteArray if decoding fails. */
-  QByteArray decodeChunked(QByteArray chunked);
+  QByteArray decodeChunked(const QByteArray &chunked);
   
   QHttpResponseHeader _header; /**< HTTP response header. */
-  QList<GeoIp> _geoips;        /**< Geo IP information in this response. */
+  QByteArray _content; /**< Geo IP information in this response. */
 };
 
 #endif

Modified: vidalia/trunk/src/vidalia/network/NetViewer.cpp
===================================================================
--- vidalia/trunk/src/vidalia/network/NetViewer.cpp	2009-05-13 18:54:28 UTC (rev 3767)
+++ vidalia/trunk/src/vidalia/network/NetViewer.cpp	2009-05-13 19:07:26 UTC (rev 3768)
@@ -461,6 +461,7 @@
 NetViewer::resolved(int id, const QList<GeoIp> &geoips)
 {
   Q_UNUSED(id);
+
   QString ip;
   RouterListItem *router;
  
@@ -469,8 +470,6 @@
     ip = geoip.ip().toString();
     QList<QString> ids = _resolveMap.values(ip);
     _resolveMap.remove(ip);
-    if (geoip.isUnknown())
-      continue; /* We don't know where this router is */
       
     /* Update their geographic location information with the results of this
      * GeoIP query. */

Modified: vidalia/trunk/src/vidalia/network/RouterListItem.cpp
===================================================================
--- vidalia/trunk/src/vidalia/network/RouterListItem.cpp	2009-05-13 18:54:28 UTC (rev 3767)
+++ vidalia/trunk/src/vidalia/network/RouterListItem.cpp	2009-05-13 19:07:26 UTC (rev 3768)
@@ -93,15 +93,15 @@
 void
 RouterListItem::setLocation(const GeoIp &geoip)
 {
-  QPixmap flag(":/images/flags/" + geoip.country().toLower() + ".png");
+  QPixmap flag(":/images/flags/" + geoip.countryCode().toLower() + ".png");
   if (!flag.isNull()) {
     setIcon(COUNTRY_COLUMN, QIcon(flag));
   }
-  setToolTip(COUNTRY_COLUMN, geoip.toLocation());
+  setToolTip(COUNTRY_COLUMN, geoip.toString());
   
   if (_rd)
-    _rd->setLocation(geoip.toLocation());
-  _country = geoip.country();
+    _rd->setLocation(geoip.toString());
+  _country = geoip.countryCode();
 }
 
 /** Overload the comparison operator. */