#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();
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;
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),
_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()
// 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;
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;
_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;
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)
{
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
}