]> git.mxchange.org Git - simgear.git/blobdiff - simgear/scene/tsync/terrasync.cxx
TerraSync log typo
[simgear.git] / simgear / scene / tsync / terrasync.cxx
index 939ff6fb6640c27ad26150406aa59ce2fa4c5c11..c48881c60e9821c3ace0c3ab0f8127ee18eab9d9 100644 (file)
 #endif
 
 #ifdef __MINGW32__
-#include <time.h>
-#include <unistd.h>
+#  include <time.h>
+#  include <unistd.h>
 #elif defined(_MSC_VER)
 #   include <io.h>
-#   ifndef HAVE_SVN_CLIENT_H
-#       include <time.h>
-#       include <process.h>
-#   endif
+#   include <time.h>
+#   include <process.h>
 #endif
 
 #include <stdlib.h>             // atoi() atof() abs() system()
 #include <signal.h>             // signal()
+#include <string.h>
 
 #include <iostream>
 #include <fstream>
 #include <simgear/compiler.h>
 
 #include "terrasync.hxx"
+
 #include <simgear/bucket/newbucket.hxx>
 #include <simgear/misc/sg_path.hxx>
+#include <simgear/misc/strutils.hxx>
 #include <simgear/threads/SGQueue.hxx>
-#include <simgear/scene/tgdb/TileCache.hxx>
 #include <simgear/misc/sg_dir.hxx>
-#include <OpenThreads/Thread>
-
-#ifdef HAVE_SVN_CLIENT_H
-#  ifdef HAVE_LIBSVN_CLIENT_1
-#    include <svn_version.h>
-#    include <svn_auth.h>
-#    include <svn_client.h>
-#    include <svn_cmdline.h>
-#    include <svn_pools.h>
-#  else
-#    undef HAVE_SVN_CLIENT_H
-#  endif
-#endif
+#include <simgear/debug/BufferedLogCallback.hxx>
+#include <simgear/props/props_io.hxx>
+#include <simgear/io/HTTPClient.hxx>
+#include <simgear/io/SVNRepository.hxx>
+#include <simgear/structure/exception.hxx>
 
-#ifdef HAVE_SVN_CLIENT_H
-    static const svn_version_checklist_t mysvn_checklist[] = {
-        { "svn_subr",   svn_subr_version },
-        { "svn_client", svn_client_version },
-        { NULL, NULL }
-    };
-    static const bool svn_built_in_available = true;
-#else
-    static const bool svn_built_in_available = false;
-#endif
+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_cmd =
-        "svn checkout -q";
+const char* svn_options =
+        "checkout -q";
 
-typedef map<string,time_t> CompletedTiles;
+namespace UpdateInterval
+{
+    // interval in seconds to allow an update to repeat after a successful update (=daily)
+    static const double SuccessfulAttempt = 24*60*60;
+    // interval in seconds to allow another update after a failed attempt (10 minutes)
+    static const double FailedAttempt     = 10*60;
+}
+
+typedef map<string,time_t> TileAgeCache;
 
 ///////////////////////////////////////////////////////////////////////////////
-// WaitingTile ////////////////////////////////////////////////////////////////
+// helper functions ///////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////////////////
-class  WaitingTile
+string stripPath(string path)
+{
+    // svn doesn't like trailing white-spaces or path separators - strip them!
+    path = simgear::strutils::strip(path);
+    size_t slen = path.length();
+    while ((slen>0)&&
+            ((path[slen-1]=='/')||(path[slen-1]=='\\')))
+    {
+        slen--;
+    }
+    return path.substr(0,slen);
+}
+
+bool hasWhitespace(string path)
+{
+    return path.find(' ')!=string::npos;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// SyncItem ////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+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<SVNRepository> 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 /////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////////////////
-class SGTerraSync::SvnThread : public OpenThreads::Thread
+class SGTerraSync::SvnThread : public SGThread
 {
 public:
    SvnThread();
@@ -118,21 +249,25 @@ 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 = server;}
-   void   setRsyncServer(string server) { _rsync_server = server;}
-   void   setLocalDir(string dir) { _local_dir = dir;}
-   string getLocalDir() { return _local_dir;}
-   void   setUseSvn(bool use_svn) { _use_svn = use_svn;}
+   void   setSvnServer(string server)       { _svn_server   = stripPath(server);}
+   void   setSvnDataServer(string server)   { _svn_data_server   = stripPath(server);}
 
-#ifdef HAVE_SVN_CLIENT_H
-   void setUseBuiltin(bool built_in) { _use_built_in = built_in;}
-#endif
+   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);}
+   string getLocalDir()                     { return _local_dir;}
+   void   setUseSvn(bool use_svn)           { _use_svn = use_svn;}
+   void   setAllowedErrorCount(int errors)  {_allowed_errors = errors;}
+
+   void   setCachePath(const SGPath& p)     {_persistentCachePath = p;}
+   void   setCacheHits(unsigned int hits)   {_cache_hits = hits;}
+   void   setUseBuiltin(bool built_in) { _use_built_in = built_in;}
 
    volatile bool _active;
    volatile bool _running;
@@ -142,43 +277,54 @@ public:
    volatile int  _updated_tile_count;
    volatile int  _success_count;
    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 syncTreeExternal(const char* dir);
 
-#ifdef HAVE_SVN_CLIENT_H
-   static int svnClientSetup(void);
-   bool syncTreeInternal(const char* dir);
+    // external model run and helpers
+    void runExternal();
+    void syncPathExternal(const SyncItem& next);
+    bool runExternalSyncCommand(const char* dir);
 
-   bool _use_built_in;
+    // internal mode run and helpers
+    void runInternal();
+    void updateSyncSlot(SyncSlot& slot);
 
-   // Things we need for doing subversion checkout - often
-   static apr_pool_t *_svn_pool;
-   static svn_client_ctx_t *_svn_ctx;
-   static svn_opt_revision_t *_svn_rev;
-   static svn_opt_revision_t *_svn_rev_peg;
-#endif
+    // 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;
 };
 
-#ifdef HAVE_SVN_CLIENT_H
-    apr_pool_t* SGTerraSync::SvnThread::_svn_pool = NULL;
-    svn_client_ctx_t* SGTerraSync::SvnThread::_svn_ctx = NULL;
-    svn_opt_revision_t* SGTerraSync::SvnThread::_svn_rev = NULL;
-    svn_opt_revision_t* SGTerraSync::SvnThread::_svn_rev_peg = NULL;
-#endif
-
 SGTerraSync::SvnThread::SvnThread() :
     _active(false),
     _running(false),
@@ -188,21 +334,16 @@ SGTerraSync::SvnThread::SvnThread() :
     _updated_tile_count(0),
     _success_count(0),
     _consecutive_errors(0),
-#ifdef HAVE_SVN_CLIENT_H
+    _allowed_errors(6),
+    _cache_hits(0),
+    _transfer_rate(0),
+    _total_kb_downloaded(0),
     _use_built_in(true),
-#endif
     _is_dirty(false),
     _stop(false),
     _use_svn(true)
 {
-#ifdef HAVE_SVN_CLIENT_H
-    int errCode = SGTerraSync::SvnThread::svnClientSetup();
-    if (errCode != EXIT_SUCCESS)
-    {
-        SG_LOG(SG_TERRAIN,SG_ALERT,
-               "Failed to initialize built-in SVN client, error = " << errCode);
-    }
-#endif
+    _http.setUserAgent("terrascenery-" SG_STRINGIZE(SG_VERSION));
 }
 
 void SGTerraSync::SvnThread::stop()
@@ -215,7 +356,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;
@@ -228,40 +369,41 @@ bool SGTerraSync::SvnThread::start()
 
     if (_local_dir=="")
     {
-        SG_LOG(SG_TERRAIN,SG_ALERT,
-               "Cannot start scenery download. No local cache directory defined.");
+        SG_LOG(SG_TERRASYNC,SG_ALERT,
+               "Cannot start scenery download. Local cache directory is undefined.");
         _fail_count++;
         _stalled = true;
         return false;
     }
-    
+
     SGPath path(_local_dir);
-    path.append("version");
-    if (path.exists())
+    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.");
+               "' does not exist. Set correct directory path or create directory folder.");
         _fail_count++;
         _stalled = true;
         return false;
     }
-#ifdef HAVE_SVN_CLIENT_H
-    _use_svn |= _use_built_in;
-#endif
 
-    if ((_use_svn)&&(_svn_server==""))
+    path.append("version");
+    if (path.exists())
     {
-        SG_LOG(SG_TERRAIN,SG_ALERT,
-               "Cannot start scenery download. Subversion scenery server is undefined.");
+        SG_LOG(SG_TERRASYNC,SG_ALERT,
+               "Cannot start scenery download. Directory '" << _local_dir <<
+               "' contains the base package. Use a separate directory.");
         _fail_count++;
         _stalled = true;
         return false;
     }
 
+    _use_svn |= _use_built_in;
+
+
     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;
@@ -276,134 +418,79 @@ bool SGTerraSync::SvnThread::start()
     _stalled = false;
     _running = true;
 
-    // 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,
-           "Starting automatic scenery download/synchronization. Directory: '" << _local_dir << "'");
+    string status;
 
-    OpenThreads::Thread::start();
-    return true;
-}
-
-// sync one directory tree
-bool SGTerraSync::SvnThread::syncTree(const char* dir)
-{
-    int rc;
-    SGPath path( _local_dir );
-
-    path.append( dir );
-    rc = path.create_dir( 0755 );
-    if (rc)
+    if (_use_svn && _use_built_in)
+        status = "Using built-in SVN support. ";
+    else if (_use_svn)
     {
-        SG_LOG(SG_TERRAIN,SG_ALERT,
-               "Cannot create directory '" << dir << "', return code = " << rc );
-        return false;
+        status = "Using external SVN utility '";
+        status += _svn_command;
+        status += "'. ";
     }
-
-#ifdef HAVE_SVN_CLIENT_H
-    if (_use_built_in)
-        return syncTreeInternal(dir);
     else
-#endif
     {
-        return syncTreeExternal(dir);
+        status = "Using RSYNC. ";
     }
-}
 
+    // 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_TERRASYNC,SG_ALERT,
+           "Starting automatic scenery download/synchronization. "
+           << status
+           << "Directory: '" << _local_dir << "'.");
+
+    SGThread::start();
+    return true;
+}
 
-#ifdef HAVE_SVN_CLIENT_H
-bool SGTerraSync::SvnThread::syncTreeInternal(const char* dir)
+bool SGTerraSync::SvnThread::runExternalSyncCommand(const char* dir)
 {
-    SG_LOG(SG_TERRAIN,SG_DEBUG, "Synchronizing scenery directory " << dir);
-    if (!_svn_pool)
+    ostringstream buf;
+    SGPath localPath( _local_dir );
+    localPath.append( dir );
+
+    if (_use_svn)
     {
-        SG_LOG(SG_TERRAIN,SG_ALERT,
-               "Built-in SVN client failed to initialize.");
-        return false;
+        buf << "\"" << _svn_command << "\" "
+            << svn_options << " "
+            << "\"" << _svn_server << "/" << dir << "\" "
+            << "\"" << localPath.str_native() << "\"";
+    } else {
+        buf << rsync_cmd << " "
+            << "\"" << _rsync_server << "/" << dir << "/\" "
+            << "\"" << localPath.str_native() << "/\"";
     }
 
-    char command[512];
-    char dest_base_dir[512];
-    snprintf( command, 512,
-        "%s/%s", _svn_server.c_str(), dir);
-    snprintf( dest_base_dir, 512,
-        "%s/%s", _local_dir.c_str(), dir);
-    svn_error_t *err = NULL;
-
-    apr_pool_t *subpool = svn_pool_create(_svn_pool);
-
-    err=0;
-#if (SVN_VER_MINOR >= 5)
-    err = svn_client_checkout3(NULL,
-            command,
-            dest_base_dir,
-            _svn_rev_peg,
-            _svn_rev,
-            svn_depth_infinity,
-            0, // ignore-externals = false
-            0, // allow unver obstructions = false
-            _svn_ctx,
-            subpool);
+    string command;
+#ifdef SG_WINDOWS
+        // 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
+        //       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.
+    command = "\"" + buf.str() + "\"";
 #else
-    // version 1.4 API
-    err = svn_client_checkout2(NULL,
-            command,
-            dest_base_dir,
-            _svn_rev_peg,
-            _svn_rev,
-            1, // recurse=true - same as svn_depth_infinity for checkout3 above
-            0, // ignore externals = false
-            _svn_ctx,
-            subpool);
+    command = buf.str();
 #endif
+    SG_LOG(SG_TERRASYNC,SG_DEBUG, "sync command '" << command << "'");
 
-    bool ReturnValue = true;
-    if (err)
-    {
-        // Report errors from the checkout attempt
-        SG_LOG(SG_TERRAIN,SG_ALERT,
-                "Failed to synchronize directory '" << dir << "', " <<
-                err->message);
-        svn_error_clear(err);
-        // try to clean up
-        err = svn_client_cleanup(dest_base_dir,
-                _svn_ctx,subpool);
-        if (!err)
-        {
-            SG_LOG(SG_TERRAIN,SG_ALERT,
-                   "SVN repository cleanup successful for '" << dir << "'.");
-        }
-        ReturnValue = false;
-    } else
-    {
-        SG_LOG(SG_TERRAIN,SG_DEBUG, "Done with scenery directory " << dir);
-    }
-    svn_pool_destroy(subpool);
-    return ReturnValue;
-}
+#ifdef SG_WINDOWS
+    // tbd: does Windows support "popen"?
+    int rc = system( command.c_str() );
+#else
+    FILE* pipe = popen( command.c_str(), "r");
+    int rc=-1;
+    // wait for external process to finish
+    if (pipe)
+        rc = pclose(pipe);
 #endif
 
-bool SGTerraSync::SvnThread::syncTreeExternal(const char* dir)
-{
-    int rc;
-    char command[512];
-    if (_use_svn)
-    {
-        snprintf( command, 512,
-            "%s %s/%s %s/%s", svn_cmd,
-            _svn_server.c_str(), dir,
-            _local_dir.c_str(), dir );
-    } else {
-        snprintf( command, 512,
-            "%s %s/%s/ %s/%s/", rsync_cmd,
-            _rsync_server.c_str(), dir,
-            _local_dir.c_str(), dir );
-    }
-    SG_LOG(SG_TERRAIN,SG_DEBUG, "sync command '" << command << "'");
-    rc = system( command );
     if (rc)
     {
-        SG_LOG(SG_TERRAIN,SG_ALERT,
+        SG_LOG(SG_TERRASYNC,SG_ALERT,
                "Failed to synchronize directory '" << dir << "', " <<
                "error code= " << rc);
         return false;
@@ -414,226 +501,471 @@ bool SGTerraSync::SvnThread::syncTreeExternal(const char* dir)
 void SGTerraSync::SvnThread::run()
 {
     _active = true;
+    initCompletedTilesPersistentCache();
+
+    {
+        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()) {
+            // default value
+            _svn_server = "http://foxtrot.mgras.net:8080/terrascenery/trunk/data/Scenery";
+        }
+    }
+
+    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;
 
-        CompletedTiles::iterator ii =
-            _completedTiles.find( next._dir );
-        time_t now = time(0);
-        if ((ii == _completedTiles.end())||
-            ((ii->second + 60*60*24) < now ))
-        {
-            _busy = true;
-            if (!syncTree(next._dir.c_str()))
-            {
-                _consecutive_errors++;
-                _fail_count++;
-            }
-            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++;
-                    _freshTiles.push_back(next);
-                    _is_dirty = true;
-                }
-            }
-            _busy = false;
-            _completedTiles[ next._dir ] = now;
+        SyncItem::Status cacheStatus = isPathCached(next);
+        if (cacheStatus != SyncItem::Invalid) {
+            _cache_hits++;
+            SG_LOG(SG_TERRASYNC, SG_DEBUG,
+                   "Cache hit for: '" << next._dir << "'");
+            next._status = cacheStatus;
+            _freshTiles.push_back(next);
+            _is_dirty = true;
+            continue;
         }
 
-        if (_consecutive_errors >= 5)
+        syncPathExternal(next);
+
+        if ((_allowed_errors >= 0)&&
+            (_consecutive_errors >= _allowed_errors))
         {
             _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;
+    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 == SVNRepository::SVN_ERROR_NOT_FOUND) {
+            notFound(slot.currentItem);
+        } else if (res != SVNRepository::SVN_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 (slot.currentItem._type == SyncItem::AIData) {
+            serverUrl = _svn_data_server;
+        }
+
+        slot.repository.reset(new SVNRepository(path, &_http));
+        slot.repository->setBaseUrl(serverUrl + "/" + slot.currentItem._dir);
+        slot.repository->update();
+
+        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());
+    }
+}
+
+void SGTerraSync::SvnThread::runInternal()
+{
+    while (!_stop) {
+        _http.update(100);
+        _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()) {
+        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. This is needed to
+    // cope with the user manipulating our cache dir
+    SGPath p(_local_dir);
+    p.append(next._dir);
+    if (!p.exists()) {
+        return SyncItem::Invalid;
+    }
+
+    time_t now = time(0);
+    return (ii->second > now) ? SyncItem::Cached : SyncItem::Invalid;
+}
+
+void SGTerraSync::SvnThread::fail(SyncItem failedItem)
+{
+    time_t now = time(0);
+    _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;
 }
 
-#ifdef HAVE_SVN_CLIENT_H
-// Configure our subversion session
-int SGTerraSync::SvnThread::svnClientSetup(void)
+void SGTerraSync::SvnThread::notFound(SyncItem item)
 {
-    // Are we already prepared?
-    if (_svn_pool) return EXIT_SUCCESS;
-    // No, so initialize svn internals generally
+    // 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();
+}
 
-#ifdef _MSC_VER
-    // there is a segfault when providing an error stream.
-    //  Apparently, calling setvbuf with a nul buffer is
-    //  not supported under msvc 7.1 ( code inside svn_cmdline_init )
-    if (svn_cmdline_init("terrasync", 0) != EXIT_SUCCESS)
-        return EXIT_FAILURE;
-#else
-    if (svn_cmdline_init("terrasync", stderr) != EXIT_SUCCESS)
-        return EXIT_FAILURE;
-#endif
-    /* Oh no! svn_cmdline_init configures the locale - affecting numeric output
-     * formats (i.e. sprintf("%f", ...)). fgfs relies on "C" locale in many places
-     * (including assumptions on required sprintf buffer sizes). Things go horribly
-     * wrong when the locale is changed to anything else but "C". Might be enough to
-     * revert LC_NUMERIC locale - but we'll do a complete revert for now...*/
-    setlocale(LC_ALL,"C");
-
-    apr_pool_t *pool;
-    apr_pool_create(&pool, NULL);
-    svn_error_t *err = NULL;
-    SVN_VERSION_DEFINE(_svn_version);
-    err = svn_ver_check_list(&_svn_version, mysvn_checklist);
-    if (err)
-        return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
-    err = svn_ra_initialize(pool);
-    if (err)
-        return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
-    char *config_dir = NULL;
-    err = svn_config_ensure(config_dir, pool);
-    if (err)
-        return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
-    err = svn_client_create_context(&_svn_ctx, pool);
-    if (err)
-        return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
-    err = svn_config_get_config(&(_svn_ctx->config),
-        config_dir, pool);
-    if (err)
-        return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
-    svn_config_t *cfg;
-    cfg = ( svn_config_t*) apr_hash_get(
-        _svn_ctx->config,
-        SVN_CONFIG_CATEGORY_CONFIG,
-        APR_HASH_KEY_STRING);
-    if (err)
-        return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
-
-    svn_auth_baton_t *ab=NULL;
-
-#if (SVN_VER_MINOR >= 6)
-    err = svn_cmdline_create_auth_baton  (&ab,
-            TRUE, NULL, NULL, config_dir, TRUE, FALSE, cfg,
-            _svn_ctx->cancel_func, _svn_ctx->cancel_baton, pool);
-#else
-    err = svn_cmdline_setup_auth_baton(&ab,
-        TRUE, NULL, NULL, config_dir, TRUE, cfg,
-        _svn_ctx->cancel_func, _svn_ctx->cancel_baton, pool);
-#endif
+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 << "'");
 
-    if (err)
-        return svn_cmdline_handle_exit_error(err, pool, "fgfs: ");
-    
-    _svn_ctx->auth_baton = ab;
-#if (SVN_VER_MINOR >= 5)
-    _svn_ctx->conflict_func = NULL;
-    _svn_ctx->conflict_baton = NULL;
-#endif
+    item._status = SyncItem::Updated;
+    if (item._type == SyncItem::Tile) {
+        _updated_tile_count++;
+    }
 
-    // Now our magic revisions
-    _svn_rev = (svn_opt_revision_t*) apr_palloc(pool, 
-        sizeof(svn_opt_revision_t));
-    if (!_svn_rev)
-        return EXIT_FAILURE;
-    _svn_rev_peg = (svn_opt_revision_t*) apr_palloc(pool, 
-        sizeof(svn_opt_revision_t));
-    if (!_svn_rev_peg)
-        return EXIT_FAILURE;
-    _svn_rev->kind = svn_opt_revision_head;
-    _svn_rev_peg->kind = svn_opt_revision_unspecified;
-    // Success if we got this far
-    _svn_pool = pool;
-    return EXIT_SUCCESS;
+    _freshTiles.push_back(item);
+    _completedTiles[ item._dir ] = now + UpdateInterval::SuccessfulAttempt;
+    _is_dirty = true;
+    writeCompletedTilesPersistentCache();
+}
+
+void SGTerraSync::SvnThread::initCompletedTilesPersistentCache()
+{
+    if (!_persistentCachePath.exists()) {
+        return;
+    }
+
+    SGPropertyNode_ptr cacheRoot(new SGPropertyNode);
+    time_t now = time(0);
+
+    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;
+        }
+
+        if (isNotFound) {
+            _completedTiles[tileName] = stamp;
+        } else {
+            _notFoundItems[tileName] = stamp;
+        }
+    }
+}
+
+void SGTerraSync::SvnThread::writeCompletedTilesPersistentCache() const
+{
+    // cache is disabled
+    if (_persistentCachePath.isNull()) {
+        return;
+    }
+
+    std::ofstream f(_persistentCachePath.c_str(), std::ios::trunc);
+    if (!f.is_open()) {
+        return;
+    }
+
+    SGPropertyNode_ptr cacheRoot(new SGPropertyNode);
+    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();
 }
-#endif
 
 ///////////////////////////////////////////////////////////////////////////////
 // SGTerraSync ////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////////////////
-SGTerraSync::SGTerraSync(SGPropertyNode_ptr root) :
+SGTerraSync::SGTerraSync() :
     _svnThread(NULL),
-    last_lat(NOWHERE),
-    last_lon(NOWHERE),
-    _terraRoot(root->getNode("/sim/terrasync",true)),
-    _tile_cache(NULL)
+    _bound(false),
+    _inited(false)
 {
     _svnThread = new SvnThread();
+    _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()
 {
-    _refresh_display = _terraRoot->getNode("refresh-display",true);
-    _terraRoot->getNode("built-in-svn-available",true)->setBoolValue(svn_built_in_available);
+    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->getNode("enabled",true)->getBoolValue())&&
+    if ((_terraRoot->getBoolValue("enabled",false))&&
          (_svnThread->_active && _svnThread->_running))
+    {
         return;
+    }
 
     _svnThread->stop();
 
-    if (_terraRoot->getNode("enabled",true)->getBoolValue())
+    if (_terraRoot->getBoolValue("enabled",false))
     {
-        _svnThread->setSvnServer(_terraRoot->getNode("svn-server",true)->getStringValue());
-        _svnThread->setRsyncServer(_terraRoot->getNode("rsync-server",true)->getStringValue());
-        _svnThread->setLocalDir(_terraRoot->getNode("scenery-dir",true)->getStringValue());
-
-    #ifdef HAVE_SVN_CLIENT_H
-        _svnThread->setUseBuiltin(_terraRoot->getNode("use-built-in-svn",true)->getBoolValue());
-    #else
-        _terraRoot->getNode("use-built-in-svn",true)->setBoolValue(false);
-    #endif
-        _svnThread->setUseSvn(_terraRoot->getNode("use-svn",true)->getBoolValue());
+        _svnThread->setSvnServer(_terraRoot->getStringValue("svn-server",""));
+        _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","")));
+        _svnThread->setCacheHits(_terraRoot->getIntValue("cache-hit", 0));
+        _svnThread->setUseSvn(_terraRoot->getBoolValue("use-svn",true));
+        _svnThread->setExtSvnUtility(_terraRoot->getStringValue("ext-svn-utility","svn"));
 
         if (_svnThread->start())
+        {
             syncAirportsModels();
+        }
     }
 
-    _stalled_node->setBoolValue(_svnThread->_stalled);
-    last_lat = NOWHERE;
-    last_lon = NOWHERE;
+    _stalledNode->setBoolValue(_svnThread->_stalled);
 }
 
 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);
     _terraRoot->getNode("error-count", true)->setAttribute(SGPropertyNode::WRITE,false);
     _terraRoot->getNode("tile-count", true)->setAttribute(SGPropertyNode::WRITE,false);
+    _terraRoot->getNode("use-built-in-svn", true)->setAttribute(SGPropertyNode::USERARCHIVE,false);
+    _terraRoot->getNode("use-svn", true)->setAttribute(SGPropertyNode::USERARCHIVE,false);
     // stalled is used as a signal handler (to connect listeners triggering GUI pop-ups)
-    _stalled_node = _terraRoot->getNode("stalled", true);
-    _stalled_node->setBoolValue(_svnThread->_stalled);
-    _stalled_node->setAttribute(SGPropertyNode::PRESERVE,true);
+    _stalledNode = _terraRoot->getNode("stalled", true);
+    _stalledNode->setBoolValue(_svnThread->_stalled);
+    _stalledNode->setAttribute(SGPropertyNode::PRESERVE,true);
 }
 
 void SGTerraSync::unbind()
 {
     _svnThread->stop();
     _tiedProperties.Untie();
+    _bound = false;
+    _inited = false;
+
+    _terraRoot.clear();
+    _stalledNode.clear();
+    _cacheHits.clear();
 }
 
 void SGTerraSync::update(double)
@@ -645,203 +977,112 @@ 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.");
             }
-            _stalled_node->setBoolValue(_svnThread->_stalled);
+            _stalledNode->setBoolValue(_svnThread->_stalled);
         }
 
-        if (!_refresh_display->getBoolValue())
-            return;
-
         while (_svnThread->hasNewTiles())
         {
-            WaitingTile next = _svnThread->getNewTile();
-            if (next._refreshScenery)
-            {
-                refreshScenery(_svnThread->getLocalDir(),next._dir);
+            SyncItem next = _svnThread->getNewTile();
+
+            if ((next._type == SyncItem::Tile) || (next._type == SyncItem::AIData)) {
+                _activeTileDirs.erase(next._dir);
             }
-        }
+        } // of freshly synced items
     }
 }
 
-void SGTerraSync::refreshScenery(SGPath path,const string& relativeDir)
+bool SGTerraSync::isIdle() {return _svnThread->isIdle();}
+
+void SGTerraSync::syncAirportsModels()
 {
-    // find tiles to be refreshed
-    if (_tile_cache)
+    static const char* bounds = "MZAJKL"; // airport sync order: K-L, A-J, M-Z
+    // note "request" method uses LIFO order, i.e. processes most recent request first
+    for( unsigned i = 0; i < strlen(bounds)/2; i++ )
     {
-        path.append(relativeDir);
-        if (path.exists())
+        for ( char synced_other = bounds[2*i]; synced_other <= bounds[2*i+1]; synced_other++ )
         {
-            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());
-                _tile_cache->refresh_tile(index);
-            }
+            ostringstream dir;
+            dir << "Airports/" << synced_other;
+            SyncItem w(dir.str(), SyncItem::AirportData);
+            _svnThread->request( w );
         }
     }
-}
-
-bool SGTerraSync::isIdle() {return _svnThread->isIdle();}
 
-void SGTerraSync::setTileCache(TileCache* tile_cache)
-{
-    _tile_cache = tile_cache;
+    SyncItem w("Models", SyncItem::SharedModels);
+    _svnThread->request( w );
 }
 
-void SGTerraSync::syncAirportsModels()
+void SGTerraSync::syncAreaByPath(const std::string& aPath)
 {
-    char synced_other;
-    for ( synced_other = 'K'; synced_other <= 'Z'; synced_other++ )
-    {
-        char dir[512];
-        snprintf( dir, 512, "Airports/%c", synced_other );
-        WaitingTile w(dir,false);
-        _svnThread->request( w );
-    }
-    for ( synced_other = 'A'; synced_other <= 'J'; synced_other++ )
+    const char* terrainobjects[3] = { "Terrain/", "Objects/",  0 };
+    for (const char** tree = &terrainobjects[0]; *tree; tree++)
     {
-        char dir[512];
-        snprintf( dir, 512, "Airports/%c", synced_other );
-        WaitingTile w(dir,false);
+        std::string dir = string(*tree) + aPath;
+        if (_activeTileDirs.find(dir) != _activeTileDirs.end()) {
+            continue;
+        }
+
+        _activeTileDirs.insert(dir);
+        SyncItem w(dir, SyncItem::Tile);
         _svnThread->request( w );
     }
-    WaitingTile w("Models",false);
-    _svnThread->request( w );
 }
 
-
-void SGTerraSync::syncArea( int lat, int lon )
+bool SGTerraSync::scheduleTile(const SGBucket& bucket)
 {
-    if ( lat < -90 || lat > 90 || lon < -180 || lon > 180 )
-        return;
-    char NS, EW;
-    int baselat, baselon;
+    std::string basePath = bucket.gen_base_path();
+    syncAreaByPath(basePath);
+    return true;
+}
 
-    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';
+bool SGTerraSync::isTileDirPending(const std::string& sceneryDir) const
+{
+    if (!_svnThread->_running) {
+        return false;
     }
-    if ( lon < 0 ) {
-        int base = (int)(lon / 10);
-        if ( lon == base * 10 ) {
-            baselon = base * 10;
-        } else {
-            baselon = (base - 1) * 10;
+
+    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;
         }
-        EW = 'w';
-    } else {
-        baselon = (int)(lon / 10) * 10;
-        EW = 'e';
     }
 
-    const char* terrainobjects[3] = { "Terrain", "Objects",  0 };
-    bool refresh=true;
-    for (const char** tree = &terrainobjects[0]; *tree; tree++)
-    {
-        char dir[512];
-        snprintf( dir, 512, "%s/%c%03d%c%02d/%c%03d%c%02d",
-            *tree,
-            EW, abs(baselon), NS, abs(baselat),
-            EW, abs(lon), NS, abs(lat) );
-        WaitingTile w(dir,refresh);
-        _svnThread->request( w );
-        refresh=false;
-    }
+    return false;
 }
 
-
-void SGTerraSync::syncAreas( int lat, int lon, int lat_dir, int lon_dir )
+void SGTerraSync::scheduleDataDir(const std::string& dataDir)
 {
-    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 (_activeTileDirs.find(dataDir) != _activeTileDirs.end()) {
+        return;
     }
 
-    // do current 1x1 degree area first
-    syncArea( lat, lon );
-}
+    _activeTileDirs.insert(dataDir);
+    SyncItem w(dataDir, SyncItem::AIData);
+    _svnThread->request( w );
 
+}
 
-bool SGTerraSync::schedulePosition(int lat, int lon)
+bool SGTerraSync::isDataDirPending(const std::string& dataDir) const
 {
-    // Ignore messages where the location does not change
-    if ( lat != last_lat || lon != last_lon )
-    {
-        SG_LOG(SG_TERRAIN,SG_DEBUG, "Requesting scenery update for position " << 
-                                    lat << "," << lon);
-        int lat_dir, lon_dir, dist;
-        if ( last_lat == NOWHERE || last_lon == NOWHERE )
-        {
-            lat_dir = lon_dir = 0;
-        } else
-        {
-            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;
-            }
-        }
-
-        SG_LOG(SG_TERRAIN,SG_DEBUG, "Scenery update for " << 
-               "lat = " << lat << ", lon = " << lon <<
-               ", lat_dir = " << lat_dir << ",  " <<
-               "lon_dir = " << lon_dir);
+    if (!_svnThread->_running) {
+        return false;
+    }
 
-        syncAreas( lat, lon, lat_dir, lon_dir );
+    return (_activeTileDirs.find(dataDir) != _activeTileDirs.end());
+}
 
-        last_lat = lat;
-        last_lon = lon;
-        return true;
-    }
-    return false;
+void SGTerraSync::reposition()
+{
+    // stub, remove
 }