]> git.mxchange.org Git - simgear.git/blobdiff - simgear/scene/tsync/terrasync.cxx
Remove unneeded local scope
[simgear.git] / simgear / scene / tsync / terrasync.cxx
index fc594ee9d80beff464f5f40ffac2865e8d15d7ee..412c3addfe758087ffabb2d96ead81caef8e6c8b 100644 (file)
 //
 // $Id$
 
-#ifdef HAVE_CONFIG_H
-#  include <simgear_config.h>
-#endif
-
+#include <simgear_config.h>
 #include <simgear/compiler.h>
 
 #ifdef HAVE_WINDOWS_H
@@ -50,7 +47,7 @@
 #include <string>
 #include <map>
 
-#include <simgear/compiler.h>
+#include <simgear/version.h>
 
 #include "terrasync.hxx"
 
 #include <simgear/props/props_io.hxx>
 #include <simgear/io/HTTPClient.hxx>
 #include <simgear/io/SVNRepository.hxx>
-
+#include <simgear/io/HTTPRepository.hxx>
+#include <simgear/io/DNSClient.hxx>
+#include <simgear/structure/exception.hxx>
+#include <simgear/math/sg_random.h>
 
 static const bool svn_built_in_available = true;
 
 using namespace simgear;
 using namespace std;
 
-const char* rsync_cmd = 
+const char* rsync_cmd =
         "rsync --verbose --archive --delete --perms --owner --group";
 
 const char* svn_options =
@@ -84,7 +84,7 @@ namespace UpdateInterval
     static const double FailedAttempt     = 10*60;
 }
 
-typedef map<string,time_t> CompletedTiles;
+typedef map<string,time_t> TileAgeCache;
 
 ///////////////////////////////////////////////////////////////////////////////
 // helper functions ///////////////////////////////////////////////////////////
@@ -108,17 +108,135 @@ bool hasWhitespace(string path)
 }
 
 ///////////////////////////////////////////////////////////////////////////////
-// WaitingTile ////////////////////////////////////////////////////////////////
+// SyncItem ////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////////////////
-class  WaitingTile
+class SyncItem
 {
 public:
-    WaitingTile(string dir,bool refresh) :
-        _dir(dir), _refreshScenery(refresh) {}
+    enum Type
+    {
+        Stop = 0,   ///< special item indicating to stop the SVNThread
+        Tile,
+        AirportData,
+        SharedModels,
+        AIData
+    };
+
+    enum Status
+    {
+        Invalid = 0,
+        Waiting,
+        Cached, ///< using already cached result
+        Updated,
+        NotFound,
+        Failed
+    };
+
+    SyncItem() :
+        _dir(),
+        _type(Stop),
+        _status(Invalid)
+    {
+    }
+
+    SyncItem(string dir, Type ty) :
+        _dir(dir),
+        _type(ty),
+        _status(Waiting)
+    {}
+
     string _dir;
-    bool _refreshScenery;
+    Type _type;
+    Status _status;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * @brief SyncSlot encapsulates a queue of sync items we will fetch
+ * serially. Multiple slots exist to sync different types of item in
+ * parallel.
+ */
+class SyncSlot
+{
+public:
+    SyncSlot() :
+        isNewDirectory(false),
+        busy(false)
+    {}
+
+    SyncItem currentItem;
+    bool isNewDirectory;
+    std::queue<SyncItem> queue;
+    std::auto_ptr<AbstractRepository> repository;
+    SGTimeStamp stamp;
+    bool busy; ///< is the slot working or idle
+
+    unsigned int nextWarnTimeout;
+};
+
+static const int SYNC_SLOT_TILES = 0; ///< Terrain and Objects sync
+static const int SYNC_SLOT_SHARED_DATA = 1; /// shared Models and Airport data
+static const int SYNC_SLOT_AI_DATA = 2; /// AI traffic and models
+static const unsigned int NUM_SYNC_SLOTS = 3;
+
+/**
+ * @brief translate a sync item type into one of the available slots.
+ * This provides the scheduling / balancing / prioritising between slots.
+ */
+static unsigned int syncSlotForType(SyncItem::Type ty)
+{
+    switch (ty) {
+    case SyncItem::Tile: return SYNC_SLOT_TILES;
+    case SyncItem::SharedModels:
+    case SyncItem::AirportData:
+        return SYNC_SLOT_SHARED_DATA;
+    case SyncItem::AIData:
+        return SYNC_SLOT_AI_DATA;
+
+    default:
+        return SYNC_SLOT_SHARED_DATA;
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Base server query
+///////////////////////////////////////////////////////////////////////////////
+
+class ServerSelectQuery : public HTTP::Request
+{
+public:
+    ServerSelectQuery() :
+        HTTP::Request("http://scenery.flightgear.org/svn-server", "GET")
+    {
+    }
+
+    std::string svnUrl() const
+    {
+        std::string s = simgear::strutils::strip(m_url);
+        if (s.at(s.length() - 1) == '/') {
+            s = s.substr(0, s.length() - 1);
+        }
+
+        return s;
+    }
+protected:
+    virtual void gotBodyData(const char* s, int n)
+    {
+        m_url.append(std::string(s, n));
+    }
+
+    virtual void onFail()
+    {
+        SG_LOG(SG_TERRASYNC, SG_ALERT, "Failed to query TerraSync SVN server");
+        HTTP::Request::onFail();
+    }
+
+private:
+    std::string m_url;
 };
 
+
 ///////////////////////////////////////////////////////////////////////////////
 // SGTerraSync::SvnThread /////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////////////////
@@ -131,13 +249,19 @@ public:
    void stop();
    bool start();
 
-   bool isIdle() {return waitingTiles.empty();}
-   void request(const WaitingTile& dir) {waitingTiles.push_front(dir);}
+    bool isIdle() {return !_busy; }
+   void request(const SyncItem& dir) {waitingTiles.push_front(dir);}
    bool isDirty() { bool r = _is_dirty;_is_dirty = false;return r;}
    bool hasNewTiles() { return !_freshTiles.empty();}
-   WaitingTile getNewTile() { return _freshTiles.pop_front();}
+   SyncItem getNewTile() { return _freshTiles.pop_front();}
 
    void   setSvnServer(string server)       { _svn_server   = stripPath(server);}
+   void   setSvnDataServer(string server)   { _svn_data_server   = stripPath(server);}
+   void   setHTTPServer(const std::string& server)
+   {
+      _httpServer = stripPath(server);
+   }
+
    void   setExtSvnUtility(string svn_util) { _svn_command  = simgear::strutils::strip(svn_util);}
    void   setRsyncServer(string server)     { _rsync_server = simgear::strutils::strip(server);}
    void   setLocalDir(string dir)           { _local_dir    = stripPath(dir);}
@@ -159,35 +283,51 @@ public:
    volatile int  _consecutive_errors;
    volatile int  _allowed_errors;
    volatile int  _cache_hits;
+   volatile int _transfer_rate;
+   // kbytes, not bytes, because bytes might overflow 2^31
+   volatile int _total_kb_downloaded;
+
 private:
    virtual void run();
-   bool syncTree(const char* dir, bool& isNewDirectory);
-   bool syncTreeExternal(const char* dir);
-    
-    bool isPathCached(const WaitingTile& next) const;
-    void syncPath(const WaitingTile& next);
-    
-   void initCompletedTilesPersistentCache();
-   void writeCompletedTilesPersistentCache() const;
-   
-   bool syncTreeInternal(const char* dir);
-   
-   bool _use_built_in;
-   HTTP::Client _http;
-   std::auto_ptr<SVNRepository> _repository;
 
+    // external model run and helpers
+    void runExternal();
+    void syncPathExternal(const SyncItem& next);
+    bool runExternalSyncCommand(const char* dir);
+
+    // internal mode run and helpers
+    void runInternal();
+    void updateSyncSlot(SyncSlot& slot);
+
+    // commond helpers between both internal and external models
+
+    SyncItem::Status isPathCached(const SyncItem& next) const;
+    void initCompletedTilesPersistentCache();
+    void writeCompletedTilesPersistentCache() const;
+    void updated(SyncItem item, bool isNewDirectory);
+    void fail(SyncItem failedItem);
+    void notFound(SyncItem notFoundItem);
+
+    bool _use_built_in;
+    HTTP::Client _http;
+    SyncSlot _syncSlots[NUM_SYNC_SLOTS];
 
    volatile bool _is_dirty;
    volatile bool _stop;
-   SGBlockingDeque <WaitingTile> waitingTiles;
-   CompletedTiles _completedTiles;
-   SGBlockingDeque <WaitingTile> _freshTiles;
+   SGBlockingDeque <SyncItem> waitingTiles;
+
+   TileAgeCache _completedTiles;
+   TileAgeCache _notFoundItems;
+
+   SGBlockingDeque <SyncItem> _freshTiles;
    bool _use_svn;
    string _svn_server;
+   string _svn_data_server;
    string _svn_command;
    string _rsync_server;
    string _local_dir;
    SGPath _persistentCachePath;
+   string _httpServer;
 };
 
 SGTerraSync::SvnThread::SvnThread() :
@@ -201,11 +341,14 @@ SGTerraSync::SvnThread::SvnThread() :
     _consecutive_errors(0),
     _allowed_errors(6),
     _cache_hits(0),
+    _transfer_rate(0),
+    _total_kb_downloaded(0),
     _use_built_in(true),
     _is_dirty(false),
     _stop(false),
     _use_svn(true)
 {
+    _http.setUserAgent("terrascenery-" SG_STRINGIZE(SIMGEAR_VERSION));
 }
 
 void SGTerraSync::SvnThread::stop()
@@ -218,7 +361,7 @@ void SGTerraSync::SvnThread::stop()
 
     // set stop flag and wake up the thread with an empty request
     _stop = true;
-    WaitingTile w("",false);
+    SyncItem w(string(), SyncItem::Stop);
     request(w);
     join();
     _running = false;
@@ -231,7 +374,7 @@ bool SGTerraSync::SvnThread::start()
 
     if (_local_dir=="")
     {
-        SG_LOG(SG_TERRAIN,SG_ALERT,
+        SG_LOG(SG_TERRASYNC,SG_ALERT,
                "Cannot start scenery download. Local cache directory is undefined.");
         _fail_count++;
         _stalled = true;
@@ -241,7 +384,7 @@ bool SGTerraSync::SvnThread::start()
     SGPath path(_local_dir);
     if (!path.exists())
     {
-        SG_LOG(SG_TERRAIN,SG_ALERT,
+        SG_LOG(SG_TERRASYNC,SG_ALERT,
                "Cannot start scenery download. Directory '" << _local_dir <<
                "' does not exist. Set correct directory path or create directory folder.");
         _fail_count++;
@@ -252,7 +395,7 @@ bool SGTerraSync::SvnThread::start()
     path.append("version");
     if (path.exists())
     {
-        SG_LOG(SG_TERRAIN,SG_ALERT,
+        SG_LOG(SG_TERRASYNC,SG_ALERT,
                "Cannot start scenery download. Directory '" << _local_dir <<
                "' contains the base package. Use a separate directory.");
         _fail_count++;
@@ -262,17 +405,10 @@ bool SGTerraSync::SvnThread::start()
 
     _use_svn |= _use_built_in;
 
-    if ((_use_svn)&&(_svn_server==""))
-    {
-        SG_LOG(SG_TERRAIN,SG_ALERT,
-               "Cannot start scenery download. Subversion scenery server is undefined.");
-        _fail_count++;
-        _stalled = true;
-        return false;
-    }
+
     if ((!_use_svn)&&(_rsync_server==""))
     {
-        SG_LOG(SG_TERRAIN,SG_ALERT,
+        SG_LOG(SG_TERRASYNC,SG_ALERT,
                "Cannot start scenery download. Rsync scenery server is undefined.");
         _fail_count++;
         _stalled = true;
@@ -304,7 +440,7 @@ bool SGTerraSync::SvnThread::start()
 
     // not really an alert - but we want to (always) see this message, so user is
     // aware we're downloading scenery (and using bandwidth).
-    SG_LOG(SG_TERRAIN,SG_ALERT,
+    SG_LOG(SG_TERRASYNC,SG_ALERT,
            "Starting automatic scenery download/synchronization. "
            << status
            << "Directory: '" << _local_dir << "'.");
@@ -313,66 +449,7 @@ bool SGTerraSync::SvnThread::start()
     return true;
 }
 
-// sync one directory tree
-bool SGTerraSync::SvnThread::syncTree(const char* dir, bool& isNewDirectory)
-{
-    int rc;
-    SGPath path( _local_dir );
-
-    path.append( dir );
-    isNewDirectory = !path.exists();
-    if (isNewDirectory)
-    {
-        rc = path.create_dir( 0755 );
-        if (rc)
-        {
-            SG_LOG(SG_TERRAIN,SG_ALERT,
-                   "Cannot create directory '" << dir << "', return code = " << rc );
-            return false;
-        }
-    }
-
-    if (_use_built_in)
-        return syncTreeInternal(dir);
-    else
-        return syncTreeExternal(dir);
-}
-    
-bool SGTerraSync::SvnThread::syncTreeInternal(const char* dir)
-{
-    ostringstream command;
-    command << _svn_server << "/" << dir;
-
-    SGPath path(_local_dir);
-    path.append(dir);
-    _repository.reset(new SVNRepository(path, &_http));
-    _repository->setBaseUrl(command.str());
-    
-    SGTimeStamp st;
-    st.stamp();
-    SG_LOG(SG_IO, SG_DEBUG, "terrasync: will sync " << command.str());
-    _repository->update();
-    
-    bool result = true;
-    while (!_stop && _repository->isDoingSync()) {
-        _http.update(100);
-    }
-    
-    if (_repository->failure() == SVNRepository::SVN_ERROR_NOT_FOUND) {
-        // this is fine, but maybe we should use a different return code
-        // in the future to higher layers can distuinguish this case
-    } else if (_repository->failure() != SVNRepository::SVN_NO_ERROR) {
-        result = false;
-    } else {
-        SG_LOG(SG_IO, SG_DEBUG, "sync of " << command.str() << " finished ("
-            << st.elapsedMSec() << " msec");
-    }
-    
-    _repository.reset();
-    return result;
-}
-
-bool SGTerraSync::SvnThread::syncTreeExternal(const char* dir)
+bool SGTerraSync::SvnThread::runExternalSyncCommand(const char* dir)
 {
     ostringstream buf;
     SGPath localPath( _local_dir );
@@ -395,7 +472,7 @@ bool SGTerraSync::SvnThread::syncTreeExternal(const char* dir)
         // windows command line parsing is just lovely...
         // to allow white spaces, the system call needs this:
         // ""C:\Program Files\something.exe" somearg "some other arg""
-        // Note: whitespace strings quoted by a pair of "" _and_ the 
+        // Note: whitespace strings quoted by a pair of "" _and_ the
         //       entire string needs to be wrapped by "" too.
         // The svn url needs forward slashes (/) as a path separator while
         // the local path needs windows-native backslash as a path separator.
@@ -403,7 +480,7 @@ bool SGTerraSync::SvnThread::syncTreeExternal(const char* dir)
 #else
     command = buf.str();
 #endif
-    SG_LOG(SG_TERRAIN,SG_DEBUG, "sync command '" << command << "'");
+    SG_LOG(SG_TERRASYNC,SG_DEBUG, "sync command '" << command << "'");
 
 #ifdef SG_WINDOWS
     // tbd: does Windows support "popen"?
@@ -418,7 +495,7 @@ bool SGTerraSync::SvnThread::syncTreeExternal(const char* dir)
 
     if (rc)
     {
-        SG_LOG(SG_TERRAIN,SG_ALERT,
+        SG_LOG(SG_TERRASYNC,SG_ALERT,
                "Failed to synchronize directory '" << dir << "', " <<
                "error code= " << rc);
         return false;
@@ -429,23 +506,125 @@ bool SGTerraSync::SvnThread::syncTreeExternal(const char* dir)
 void SGTerraSync::SvnThread::run()
 {
     _active = true;
-    
     initCompletedTilesPersistentCache();
-    
+
+    if (_httpServer == "automatic" ) {
+
+        //TODO: make DN and service settable from properties
+        DNS::NAPTRRequest * naptrRequest = new DNS::NAPTRRequest("terrasync.flightgear.org");
+        naptrRequest->qservice = "ws20";
+
+        naptrRequest->qflags = "U";
+        DNS::Request_ptr r(naptrRequest);
+
+        DNS::Client dnsClient;
+        dnsClient.makeRequest(r);
+        while( !r->isComplete() && !r->isTimeout() )
+          dnsClient.update(0);
+
+        if( naptrRequest->entries.empty() ) {
+            SG_LOG(SG_TERRASYNC, SG_ALERT, "ERROR: automatic terrasync http-server requested, but no DNS entry found.");
+            _httpServer = "";
+        } else {
+            // walk through responses, they are ordered by 1. order and 2. preference
+            // For now, only take entries with lowest order
+            // TODO: try all available servers in the order given by preferenc and order
+            int order = naptrRequest->entries[0]->order;
+
+            // get all servers with this order and the same (for now only lowest preference)
+            DNS::NAPTRRequest::NAPTR_list availableServers;
+                for( DNS::NAPTRRequest::NAPTR_list::const_iterator it = naptrRequest->entries.begin();
+                  it != naptrRequest->entries.end();
+                  ++it ) {
+
+                if( (*it)->order != order )
+                    continue;
+
+                string regex = (*it)->regexp;
+                if( false == simgear::strutils::starts_with( (*it)->regexp, "!^.*$!" ) ) {
+                    SG_LOG(SG_TERRASYNC,SG_WARN, "ignoring unsupported regexp: " << (*it)->regexp );
+                    continue;
+                }
+
+                if( false == simgear::strutils::ends_with( (*it)->regexp, "!" ) ) {
+                    SG_LOG(SG_TERRASYNC,SG_WARN, "ignoring unsupported regexp: " << (*it)->regexp );
+                    continue;
+                }
+
+                // always use first entry
+                if( availableServers.empty() || (*it)->preference == availableServers[0]->preference) {
+                    SG_LOG(SG_TERRASYNC,SG_DEBUG, "available server regexp: " << (*it)->regexp );
+                    availableServers.push_back( *it );
+                }
+          }
+
+          // now pick a random entry from the available servers
+          DNS::NAPTRRequest::NAPTR_list::size_type idx = sg_random() * availableServers.size();
+          _httpServer = availableServers[idx]->regexp;
+          _httpServer = _httpServer.substr( 6, _httpServer.length()-7 ); // strip search pattern and separators
+
+          SG_LOG(SG_TERRASYNC,SG_INFO, "picking entry # " << idx << ", server is " << _httpServer );
+        }
+    }
+    if( _httpServer.empty() ) { // don't resolve SVN server is HTTP server is set
+        if ( _svn_server.empty()) {
+            SG_LOG(SG_TERRASYNC,SG_INFO, "Querying closest TerraSync server");
+            ServerSelectQuery* ssq = new ServerSelectQuery;
+            HTTP::Request_ptr req = ssq;
+            _http.makeRequest(req);
+            while (!req->isComplete()) {
+                _http.update(20);
+            }
+
+            if (req->readyState() == HTTP::Request::DONE) {
+                _svn_server = ssq->svnUrl();
+                SG_LOG(SG_TERRASYNC,SG_INFO, "Closest TerraSync server:" << _svn_server);
+            } else {
+                SG_LOG(SG_TERRASYNC,SG_WARN, "Failed to query closest TerraSync server");
+            }
+        } else {
+            SG_LOG(SG_TERRASYNC,SG_INFO, "Explicit: TerraSync server:" << _svn_server);
+        }
+
+        if (_svn_server.empty()) {
+#if WE_HAVE_A_DEFAULT_SERVER_BUT_WE_DONT_THIS_URL_IS_NO_LONGER_VALID
+            // default value
+            _svn_server = "http://foxtrot.mgras.net:8080/terrascenery/trunk/data/Scenery";
+#endif
+        }
+    }
+
+    if (_use_built_in) {
+        runInternal();
+    } else {
+        runExternal();
+    }
+
+    _active = false;
+    _running = false;
+    _is_dirty = true;
+}
+
+void SGTerraSync::SvnThread::runExternal()
+{
     while (!_stop)
     {
-        WaitingTile next = waitingTiles.pop_front();
+        SyncItem next = waitingTiles.pop_front();
         if (_stop)
            break;
 
-        if (isPathCached(next)) {
+        SyncItem::Status cacheStatus = isPathCached(next);
+        if (cacheStatus != SyncItem::Invalid) {
             _cache_hits++;
-            SG_LOG(SG_TERRAIN, SG_DEBUG,
+            SG_LOG(SG_TERRASYNC, SG_DEBUG,
                    "Cache hit for: '" << next._dir << "'");
+            next._status = cacheStatus;
+            _freshTiles.push_back(next);
+            _is_dirty = true;
             continue;
         }
-        
-        syncPath(next);
+
+        syncPathExternal(next);
 
         if ((_allowed_errors >= 0)&&
             (_consecutive_errors >= _allowed_errors))
@@ -453,66 +632,235 @@ void SGTerraSync::SvnThread::run()
             _stalled = true;
             _stop = true;
         }
+    } // of thread running loop
+}
+
+void SGTerraSync::SvnThread::syncPathExternal(const SyncItem& next)
+{
+    _busy = true;
+    SGPath path( _local_dir );
+    path.append( next._dir );
+    bool isNewDirectory = !path.exists();
+
+    try {
+        if (isNewDirectory) {
+            int rc = path.create_dir( 0755 );
+            if (rc) {
+                SG_LOG(SG_TERRASYNC,SG_ALERT,
+                       "Cannot create directory '" << path << "', return code = " << rc );
+                throw sg_exception("Cannot create directory for terrasync", path.str());
+            }
+        }
+
+        if (!runExternalSyncCommand(next._dir.c_str())) {
+            throw sg_exception("Running external sync command failed");
+        }
+    } catch (sg_exception& e) {
+        fail(next);
+        _busy = false;
+        return;
     }
 
-    _active = false;
-    _running = false;
-    _is_dirty = true;
+    updated(next, isNewDirectory);
+    _busy = false;
+}
+
+void SGTerraSync::SvnThread::updateSyncSlot(SyncSlot &slot)
+{
+    if (slot.repository.get()) {
+        if (slot.repository->isDoingSync()) {
+#if 1
+            if (slot.stamp.elapsedMSec() > (int)slot.nextWarnTimeout) {
+                SG_LOG(SG_TERRASYNC, SG_INFO, "sync taking a long time:" << slot.currentItem._dir << " taken " << slot.stamp.elapsedMSec());
+                SG_LOG(SG_TERRASYNC, SG_INFO, "HTTP request count:" << _http.hasActiveRequests());
+                slot.nextWarnTimeout += 10000;
+            }
+#endif
+            return; // easy, still working
+        }
+
+        // check result
+        SVNRepository::ResultCode res = slot.repository->failure();
+        if (res == AbstractRepository::REPO_ERROR_NOT_FOUND) {
+            notFound(slot.currentItem);
+        } else if (res != AbstractRepository::REPO_NO_ERROR) {
+            fail(slot.currentItem);
+        } else {
+            updated(slot.currentItem, slot.isNewDirectory);
+            SG_LOG(SG_TERRASYNC, SG_DEBUG, "sync of " << slot.repository->baseUrl() << " finished ("
+                   << slot.stamp.elapsedMSec() << " msec");
+        }
+
+        // whatever happened, we're done with this repository instance
+        slot.busy = false;
+        slot.repository.reset();
+    }
+
+    // init and start sync of the next repository
+    if (!slot.queue.empty()) {
+        slot.currentItem = slot.queue.front();
+        slot.queue.pop();
+
+        SGPath path(_local_dir);
+        path.append(slot.currentItem._dir);
+        slot.isNewDirectory = !path.exists();
+        if (slot.isNewDirectory) {
+            int rc = path.create_dir( 0755 );
+            if (rc) {
+                SG_LOG(SG_TERRASYNC,SG_ALERT,
+                       "Cannot create directory '" << path << "', return code = " << rc );
+                fail(slot.currentItem);
+                return;
+            }
+        } // of creating directory step
+
+        string serverUrl(_svn_server);
+        if (!_httpServer.empty()) {
+          slot.repository.reset(new HTTPRepository(path, &_http));
+          serverUrl = _httpServer;
+        } else {
+          if (slot.currentItem._type == SyncItem::AIData) {
+              serverUrl = _svn_data_server;
+          }
+          slot.repository.reset(new SVNRepository(path, &_http));
+        }
+
+        slot.repository->setBaseUrl(serverUrl + "/" + slot.currentItem._dir);
+        try {
+            slot.repository->update();
+        } catch (sg_exception& e) {
+            SG_LOG(SG_TERRASYNC, SG_INFO, "sync of " << slot.repository->baseUrl() << " failed to start with error:"
+                   << e.getFormattedMessage());
+            fail(slot.currentItem);
+            slot.busy = false;
+            slot.repository.reset();
+            return;
+        }
+
+        slot.nextWarnTimeout = 20000;
+        slot.stamp.stamp();
+        slot.busy = true;
+        SG_LOG(SG_TERRASYNC, SG_INFO, "sync of " << slot.repository->baseUrl() << " started, queue size is " << slot.queue.size());
+    }
 }
 
-bool SGTerraSync::SvnThread::isPathCached(const WaitingTile& next) const
+void SGTerraSync::SvnThread::runInternal()
 {
-    CompletedTiles::const_iterator ii = _completedTiles.find( next._dir );
+    while (!_stop) {
+        try {
+            _http.update(100);
+        } catch (sg_exception& e) {
+            SG_LOG(SG_TERRASYNC, SG_WARN, "failure doing HTTP update" << e.getFormattedMessage());
+        }
+
+        _transfer_rate = _http.transferRateBytesPerSec();
+        // convert from bytes to kbytes
+        _total_kb_downloaded = static_cast<int>(_http.totalBytesDownloaded() / 1024);
+
+        if (_stop)
+            break;
+
+        // drain the waiting tiles queue into the sync slot queues.
+        while (!waitingTiles.empty()) {
+            SyncItem next = waitingTiles.pop_front();
+            SyncItem::Status cacheStatus = isPathCached(next);
+            if (cacheStatus != SyncItem::Invalid) {
+                _cache_hits++;
+                SG_LOG(SG_TERRASYNC, SG_DEBUG, "\nTerraSync Cache hit for: '" << next._dir << "'");
+                next._status = cacheStatus;
+                _freshTiles.push_back(next);
+                _is_dirty = true;
+                continue;
+            }
+
+            unsigned int slot = syncSlotForType(next._type);
+            _syncSlots[slot].queue.push(next);
+        }
+
+        bool anySlotBusy = false;
+        // update each sync slot in turn
+        for (unsigned int slot=0; slot < NUM_SYNC_SLOTS; ++slot) {
+            updateSyncSlot(_syncSlots[slot]);
+            anySlotBusy |= _syncSlots[slot].busy;
+        }
+
+        _busy = anySlotBusy;
+        if (!anySlotBusy) {
+            // wait on the blocking deque here, otherwise we spin
+            // the loop very fast, since _http::update with no connections
+            // active returns immediately.
+            waitingTiles.waitOnNotEmpty();
+        }
+    } // of thread running loop
+}
+
+SyncItem::Status SGTerraSync::SvnThread::isPathCached(const SyncItem& next) const
+{
+    TileAgeCache::const_iterator ii = _completedTiles.find( next._dir );
     if (ii == _completedTiles.end()) {
-        return false;
+        ii = _notFoundItems.find( next._dir );
+        // Invalid means 'not cached', otherwise we want to return to
+        // higher levels the cache status
+        return (ii == _notFoundItems.end()) ? SyncItem::Invalid : SyncItem::NotFound;
     }
-    
-    // check if the path still physically exists
+
+    // check if the path still physically exists. This is needed to
+    // cope with the user manipulating our cache dir
     SGPath p(_local_dir);
     p.append(next._dir);
     if (!p.exists()) {
-        return false;
+        return SyncItem::Invalid;
     }
-    
+
     time_t now = time(0);
-    return (ii->second > now);
+    return (ii->second > now) ? SyncItem::Cached : SyncItem::Invalid;
 }
 
-void SGTerraSync::SvnThread::syncPath(const WaitingTile& next)
+void SGTerraSync::SvnThread::fail(SyncItem failedItem)
 {
-    bool isNewDirectory = false;
     time_t now = time(0);
-    
-    _busy = true;
-    if (!syncTree(next._dir.c_str(),isNewDirectory))
-    {
-        _consecutive_errors++;
-        _fail_count++;
-        _completedTiles[ next._dir ] = now + UpdateInterval::FailedAttempt;
-    }
-    else
-    {
-        _consecutive_errors = 0;
-        _success_count++;
-        SG_LOG(SG_TERRAIN,SG_INFO,
-               "Successfully synchronized directory '" << next._dir << "'");
-        if (next._refreshScenery)
-        {
-            // updated a tile
-            _updated_tile_count++;
-            if (isNewDirectory)
-            {
-                // for now only report new directories to refresh display
-                // (i.e. only when ocean needs to be replaced with actual data)
-                _freshTiles.push_back(next);
-                _is_dirty = true;
-            }
-        }
-        
-        _completedTiles[ next._dir ] = now + UpdateInterval::SuccessfulAttempt;
-        writeCompletedTilesPersistentCache();
+    _consecutive_errors++;
+    _fail_count++;
+    failedItem._status = SyncItem::Failed;
+    _freshTiles.push_back(failedItem);
+    SG_LOG(SG_TERRASYNC,SG_INFO,
+           "Failed to sync'" << failedItem._dir << "'");
+    _completedTiles[ failedItem._dir ] = now + UpdateInterval::FailedAttempt;
+    _is_dirty = true;
+}
+
+void SGTerraSync::SvnThread::notFound(SyncItem item)
+{
+    // treat not found as authorative, so use the same cache expiry
+    // as succesful download. Important for MP models and similar so
+    // we don't spam the server with lookups for models that don't
+    // exist
+
+    time_t now = time(0);
+    item._status = SyncItem::NotFound;
+    _freshTiles.push_back(item);
+    _is_dirty = true;
+    _notFoundItems[ item._dir ] = now + UpdateInterval::SuccessfulAttempt;
+    writeCompletedTilesPersistentCache();
+}
+
+void SGTerraSync::SvnThread::updated(SyncItem item, bool isNewDirectory)
+{
+    time_t now = time(0);
+    _consecutive_errors = 0;
+    _success_count++;
+    SG_LOG(SG_TERRASYNC,SG_INFO,
+           "Successfully synchronized directory '" << item._dir << "'");
+
+    item._status = SyncItem::Updated;
+    if (item._type == SyncItem::Tile) {
+        _updated_tile_count++;
     }
-    _busy = false;
+
+    _freshTiles.push_back(item);
+    _completedTiles[ item._dir ] = now + UpdateInterval::SuccessfulAttempt;
+    _is_dirty = true;
+    writeCompletedTilesPersistentCache();
 }
 
 void SGTerraSync::SvnThread::initCompletedTilesPersistentCache()
@@ -520,20 +868,31 @@ void SGTerraSync::SvnThread::initCompletedTilesPersistentCache()
     if (!_persistentCachePath.exists()) {
         return;
     }
-    
+
     SGPropertyNode_ptr cacheRoot(new SGPropertyNode);
     time_t now = time(0);
-    
-    readProperties(_persistentCachePath.str(), cacheRoot);
+
+    try {
+        readProperties(_persistentCachePath.str(), cacheRoot);
+    } catch (sg_exception& e) {
+        SG_LOG(SG_TERRASYNC, SG_INFO, "corrupted persistent cache, discarding");
+        return;
+    }
+
     for (int i=0; i<cacheRoot->nChildren(); ++i) {
         SGPropertyNode* entry = cacheRoot->getChild(i);
+        bool isNotFound = (strcmp(entry->getName(), "not-found") == 0);
         string tileName = entry->getStringValue("path");
         time_t stamp = entry->getIntValue("stamp");
         if (stamp < now) {
             continue;
         }
-        
-        _completedTiles[tileName] = stamp;
+
+        if (isNotFound) {
+            _completedTiles[tileName] = stamp;
+        } else {
+            _notFoundItems[tileName] = stamp;
+        }
     }
 }
 
@@ -543,20 +902,27 @@ void SGTerraSync::SvnThread::writeCompletedTilesPersistentCache() const
     if (_persistentCachePath.isNull()) {
         return;
     }
-    
+
     std::ofstream f(_persistentCachePath.c_str(), std::ios::trunc);
     if (!f.is_open()) {
         return;
     }
-    
+
     SGPropertyNode_ptr cacheRoot(new SGPropertyNode);
-    CompletedTiles::const_iterator it = _completedTiles.begin();
+    TileAgeCache::const_iterator it = _completedTiles.begin();
     for (; it != _completedTiles.end(); ++it) {
         SGPropertyNode* entry = cacheRoot->addChild("entry");
         entry->setStringValue("path", it->first);
         entry->setIntValue("stamp", it->second);
     }
-    
+
+    it = _notFoundItems.begin();
+    for (; it != _notFoundItems.end(); ++it) {
+        SGPropertyNode* entry = cacheRoot->addChild("not-found");
+        entry->setStringValue("path", it->first);
+        entry->setIntValue("stamp", it->second);
+    }
+
     writeProperties(f, cacheRoot, true /* write_all */);
     f.close();
 }
@@ -564,54 +930,80 @@ void SGTerraSync::SvnThread::writeCompletedTilesPersistentCache() const
 ///////////////////////////////////////////////////////////////////////////////
 // SGTerraSync ////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////////////////
-SGTerraSync::SGTerraSync(SGPropertyNode_ptr root) :
+SGTerraSync::SGTerraSync() :
     _svnThread(NULL),
-    last_lat(NOWHERE),
-    last_lon(NOWHERE),
-    _terraRoot(root->getNode("/sim/terrasync",true)),
-    _refreshCb(NULL),
-    _userCbData(NULL)
+    _bound(false),
+    _inited(false)
 {
     _svnThread = new SvnThread();
-    _log = new BufferedLogCallback(SG_TERRAIN, SG_INFO);
+    _log = new BufferedLogCallback(SG_TERRASYNC, SG_INFO);
     _log->truncateAt(255);
-    
+
     sglog().addCallback(_log);
 }
 
 SGTerraSync::~SGTerraSync()
 {
-    _tiedProperties.Untie();
     delete _svnThread;
     _svnThread = NULL;
     sglog().removeCallback(_log);
     delete _log;
+     _tiedProperties.Untie();
+}
+
+void SGTerraSync::setRoot(SGPropertyNode_ptr root)
+{
+    _terraRoot = root->getNode("/sim/terrasync",true);
 }
 
 void SGTerraSync::init()
 {
-    _refreshDisplay = _terraRoot->getNode("refresh-display",true);
+    if (_inited) {
+        return;
+    }
+
+    _inited = true;
+
+    assert(_terraRoot);
     _terraRoot->setBoolValue("built-in-svn-available",svn_built_in_available);
+
     reinit();
 }
 
+void SGTerraSync::shutdown()
+{
+     _svnThread->stop();
+}
+
 void SGTerraSync::reinit()
 {
     // do not reinit when enabled and we're already up and running
     if ((_terraRoot->getBoolValue("enabled",false))&&
          (_svnThread->_active && _svnThread->_running))
+    {
         return;
+    }
 
     _svnThread->stop();
 
     if (_terraRoot->getBoolValue("enabled",false))
     {
         _svnThread->setSvnServer(_terraRoot->getStringValue("svn-server",""));
+        std::string httpServer(_terraRoot->getStringValue("http-server",""));
+        _svnThread->setHTTPServer(httpServer);
+        _svnThread->setSvnDataServer(_terraRoot->getStringValue("svn-data-server",""));
         _svnThread->setRsyncServer(_terraRoot->getStringValue("rsync-server",""));
         _svnThread->setLocalDir(_terraRoot->getStringValue("scenery-dir",""));
         _svnThread->setAllowedErrorCount(_terraRoot->getIntValue("max-errors",5));
         _svnThread->setUseBuiltin(_terraRoot->getBoolValue("use-built-in-svn",true));
-        _svnThread->setCachePath(SGPath(_terraRoot->getStringValue("cache-path","")));
+
+        if (httpServer.empty()) {
+            // HTTP doesn't benefit from using the persistent cache
+            _svnThread->setCachePath(SGPath(_terraRoot->getStringValue("cache-path","")));
+        } else {
+            SG_LOG(SG_TERRASYNC, SG_INFO, "HTTP repository selected, disabling persistent cache");
+        }
+
         _svnThread->setCacheHits(_terraRoot->getIntValue("cache-hit", 0));
         _svnThread->setUseSvn(_terraRoot->getBoolValue("use-svn",true));
         _svnThread->setExtSvnUtility(_terraRoot->getStringValue("ext-svn-utility","svn"));
@@ -619,15 +1011,6 @@ void SGTerraSync::reinit()
         if (_svnThread->start())
         {
             syncAirportsModels();
-            if (last_lat != NOWHERE && last_lon != NOWHERE)
-            {
-                // reschedule most recent position
-                int lat = last_lat;
-                int lon = last_lon;
-                last_lat = NOWHERE;
-                last_lon = NOWHERE;
-                schedulePosition(lat, lon);
-            }
         }
     }
 
@@ -636,13 +1019,23 @@ void SGTerraSync::reinit()
 
 void SGTerraSync::bind()
 {
+    if (_bound) {
+        return;
+    }
+
+    _bound = true;
     _tiedProperties.Tie( _terraRoot->getNode("busy", true), (bool*) &_svnThread->_busy );
     _tiedProperties.Tie( _terraRoot->getNode("active", true), (bool*) &_svnThread->_active );
     _tiedProperties.Tie( _terraRoot->getNode("update-count", true), (int*) &_svnThread->_success_count );
     _tiedProperties.Tie( _terraRoot->getNode("error-count", true), (int*) &_svnThread->_fail_count );
     _tiedProperties.Tie( _terraRoot->getNode("tile-count", true), (int*) &_svnThread->_updated_tile_count );
     _tiedProperties.Tie( _terraRoot->getNode("cache-hits", true), (int*) &_svnThread->_cache_hits );
-    
+    _tiedProperties.Tie( _terraRoot->getNode("transfer-rate-bytes-sec", true), (int*) &_svnThread->_transfer_rate );
+
+    // use kbytes here because propety doesn't support 64-bit and we might conceivably
+    // download more than 2G in a single session
+    _tiedProperties.Tie( _terraRoot->getNode("downloaded-kbytes", true), (int*) &_svnThread->_total_kb_downloaded );
+
     _terraRoot->getNode("busy", true)->setAttribute(SGPropertyNode::WRITE,false);
     _terraRoot->getNode("active", true)->setAttribute(SGPropertyNode::WRITE,false);
     _terraRoot->getNode("update-count", true)->setAttribute(SGPropertyNode::WRITE,false);
@@ -660,6 +1053,12 @@ void SGTerraSync::unbind()
 {
     _svnThread->stop();
     _tiedProperties.Untie();
+    _bound = false;
+    _inited = false;
+
+    _terraRoot.clear();
+    _stalledNode.clear();
+    _cacheHits.clear();
 }
 
 void SGTerraSync::update(double)
@@ -671,63 +1070,31 @@ void SGTerraSync::update(double)
         {
             if (_svnThread->_stalled)
             {
-                SG_LOG(SG_TERRAIN,SG_ALERT,
+                SG_LOG(SG_TERRASYNC,SG_ALERT,
                        "Automatic scenery download/synchronization stalled. Too many errors.");
             }
             else
             {
                 // not really an alert - just always show this message
-                SG_LOG(SG_TERRAIN,SG_ALERT,
+                SG_LOG(SG_TERRASYNC,SG_ALERT,
                         "Automatic scenery download/synchronization has stopped.");
             }
             _stalledNode->setBoolValue(_svnThread->_stalled);
         }
 
-        if (!_refreshDisplay->getBoolValue())
-            return;
-
         while (_svnThread->hasNewTiles())
         {
-            WaitingTile next = _svnThread->getNewTile();
-            if (next._refreshScenery)
-            {
-                refreshScenery(_svnThread->getLocalDir(),next._dir);
-            }
-        }
-    }
-}
+            SyncItem next = _svnThread->getNewTile();
 
-void SGTerraSync::refreshScenery(SGPath path,const string& relativeDir)
-{
-    // find tiles to be refreshed
-    if (_refreshCb)
-    {
-        path.append(relativeDir);
-        if (path.exists())
-        {
-            simgear::Dir dir(path);
-            //TODO need to be smarter here. only update tiles which actually
-            // changed recently. May also be possible to use information from the
-            // built-in SVN client directly (instead of checking directory contents).
-            PathList tileList = dir.children(simgear::Dir::TYPE_FILE, ".stg");
-            for (unsigned int i=0; i<tileList.size(); ++i)
-            {
-                // reload scenery tile
-                long index = atoi(tileList[i].file().c_str());
-                _refreshCb(_userCbData, index);
+            if ((next._type == SyncItem::Tile) || (next._type == SyncItem::AIData)) {
+                _activeTileDirs.erase(next._dir);
             }
-        }
+        } // of freshly synced items
     }
 }
 
 bool SGTerraSync::isIdle() {return _svnThread->isIdle();}
 
-void SGTerraSync::setTileRefreshCb(SGTerraSyncCallback refreshCb, void* userCbData)
-{
-    _refreshCb = refreshCb;
-    _userCbData = userCbData;
-}
-
 void SGTerraSync::syncAirportsModels()
 {
     static const char* bounds = "MZAJKL"; // airport sync order: K-L, A-J, M-Z
@@ -738,137 +1105,77 @@ void SGTerraSync::syncAirportsModels()
         {
             ostringstream dir;
             dir << "Airports/" << synced_other;
-            WaitingTile w(dir.str(),false);
+            SyncItem w(dir.str(), SyncItem::AirportData);
             _svnThread->request( w );
         }
     }
-    WaitingTile w("Models",false);
+
+    SyncItem w("Models", SyncItem::SharedModels);
     _svnThread->request( w );
 }
 
-
-void SGTerraSync::syncArea( int lat, int lon )
+void SGTerraSync::syncAreaByPath(const std::string& aPath)
 {
-    if ( lat < -90 || lat > 90 || lon < -180 || lon > 180 )
-        return;
-    char NS, EW;
-    int baselat, baselon;
-
-    if ( lat < 0 ) {
-        int base = (int)(lat / 10);
-        if ( lat == base * 10 ) {
-            baselat = base * 10;
-        } else {
-            baselat = (base - 1) * 10;
-        }
-        NS = 's';
-    } else {
-        baselat = (int)(lat / 10) * 10;
-        NS = 'n';
-    }
-    if ( lon < 0 ) {
-        int base = (int)(lon / 10);
-        if ( lon == base * 10 ) {
-            baselon = base * 10;
-        } else {
-            baselon = (base - 1) * 10;
-        }
-        EW = 'w';
-    } else {
-        baselon = (int)(lon / 10) * 10;
-        EW = 'e';
-    }
-
-    const char* terrainobjects[3] = { "Terrain", "Objects",  0 };
-    bool refresh=true;
+    const char* terrainobjects[3] = { "Terrain/", "Objects/",  0 };
     for (const char** tree = &terrainobjects[0]; *tree; tree++)
     {
-        ostringstream dir;
-        dir << *tree << "/" << setfill('0')
-            << EW << setw(3) << abs(baselon) << NS << setw(2) << abs(baselat) << "/"
-            << EW << setw(3) << abs(lon)     << NS << setw(2) << abs(lat);
-        WaitingTile w(dir.str(),refresh);
+        std::string dir = string(*tree) + aPath;
+        if (_activeTileDirs.find(dir) != _activeTileDirs.end()) {
+            continue;
+        }
+
+        _activeTileDirs.insert(dir);
+        SyncItem w(dir, SyncItem::Tile);
         _svnThread->request( w );
-        refresh=false;
     }
 }
 
+bool SGTerraSync::scheduleTile(const SGBucket& bucket)
+{
+    std::string basePath = bucket.gen_base_path();
+    syncAreaByPath(basePath);
+    return true;
+}
 
-void SGTerraSync::syncAreas( int lat, int lon, int lat_dir, int lon_dir )
+bool SGTerraSync::isTileDirPending(const std::string& sceneryDir) const
 {
-    if ( lat_dir == 0 && lon_dir == 0 ) {
-        // do surrounding 8 1x1 degree areas.
-        for ( int i = lat - 1; i <= lat + 1; ++i ) {
-            for ( int j = lon - 1; j <= lon + 1; ++j ) {
-                if ( i != lat || j != lon ) {
-                    syncArea( i, j );
-                }
-            }
-        }
-    } else {
-        if ( lat_dir != 0 ) {
-            syncArea( lat + lat_dir, lon - 1 );
-            syncArea( lat + lat_dir, lon + 1 );
-            syncArea( lat + lat_dir, lon );
-        }
-        if ( lon_dir != 0 ) {
-            syncArea( lat - 1, lon + lon_dir );
-            syncArea( lat + 1, lon + lon_dir );
-            syncArea( lat, lon + lon_dir );
+    if (!_svnThread->_running) {
+        return false;
+    }
+
+    const char* terrainobjects[3] = { "Terrain/", "Objects/",  0 };
+    for (const char** tree = &terrainobjects[0]; *tree; tree++) {
+        string s = *tree + sceneryDir;
+        if (_activeTileDirs.find(s) != _activeTileDirs.end()) {
+            return true;
         }
     }
 
-    // do current 1x1 degree area first
-    syncArea( lat, lon );
+    return false;
 }
 
-
-bool SGTerraSync::schedulePosition(int lat, int lon)
+void SGTerraSync::scheduleDataDir(const std::string& dataDir)
 {
-    bool Ok = false;
+    if (_activeTileDirs.find(dataDir) != _activeTileDirs.end()) {
+        return;
+    }
 
-    // Ignore messages where the location does not change
-    if ( lat != last_lat || lon != last_lon )
-    {
-        if (_svnThread->_running)
-        {
-            SG_LOG(SG_TERRAIN,SG_DEBUG, "Requesting scenery update for position " <<
-                                        lat << "," << lon);
-            int lat_dir=0;
-            int lon_dir=0;
-            if ( last_lat != NOWHERE && last_lon != NOWHERE )
-            {
-                int dist = lat - last_lat;
-                if ( dist != 0 )
-                {
-                    lat_dir = dist / abs(dist);
-                }
-                else
-                {
-                    lat_dir = 0;
-                }
-                dist = lon - last_lon;
-                if ( dist != 0 )
-                {
-                    lon_dir = dist / abs(dist);
-                } else
-                {
-                    lon_dir = 0;
-                }
-            }
+    _activeTileDirs.insert(dataDir);
+    SyncItem w(dataDir, SyncItem::AIData);
+    _svnThread->request( w );
 
-            SG_LOG(SG_TERRAIN,SG_DEBUG, "Scenery update for " <<
-                   "lat = " << lat << ", lon = " << lon <<
-                   ", lat_dir = " << lat_dir << ",  " <<
-                   "lon_dir = " << lon_dir);
+}
 
-            syncAreas( lat, lon, lat_dir, lon_dir );
-            Ok = true;
-        }
-        last_lat = lat;
-        last_lon = lon;
+bool SGTerraSync::isDataDirPending(const std::string& dataDir) const
+{
+    if (!_svnThread->_running) {
+        return false;
     }
 
-    return Ok;
+    return (_activeTileDirs.find(dataDir) != _activeTileDirs.end());
 }
 
+void SGTerraSync::reposition()
+{
+    // stub, remove
+}