Compare commits

...
Author SHA1 Message Date
Automatic Release Builder 6e0c39bb68 new version: 2020.3.4 2020-11-30 11:17:52 +00:00
Julian Smith f20b416cfe simgear/props/props.cxx: use rmutex to protect SGPropertyNodeListeners.
Also added asserts to check _num_iterators always >= 0.
2020-11-29 20:16:46 +00:00
Scott Giese 47e06b5216 METAR: mitigate wind sensor failures 2020-11-29 16:25:01 +00:00
James Turner bfcdf22705 TerraSync: fix crashes with null file return
Not sure how this is happening, but, check for a null file object
in FileGetRequest::onDone.
2020-11-29 16:23:44 +00:00
Stuart Buchanan d0db407faa Set minimum expiry time on STG nodes. 2020-11-29 16:23:40 +00:00
Automatic Release Builder d95b1c0441 new version: 2020.3.3 2020-11-12 11:14:23 +00:00
James Turner 837ba86d57 DNSClient: own requests, and cancel them on timeout
Fixes crashes where a request times-out, but then is completed by
UDN sometime afterwards, with a free-d object. Have the DNS::Client own
requests, and be able to retrieve the udns_query to cancel them, in 
the timeout case.

Fixes a couple of Sentry reports.
2020-11-12 09:39:58 +00:00
Automatic Release Builder 0cb1b463e1 Catalogs: fix ownership of new Catalogs
When doing the initial download of a Catalog, ensure we still keep
an owning ref to it.
2020-11-11 21:18:57 +00:00
Automatic Release Builder 8a772c8edd new version: 2020.3.2 2020-11-04 22:08:28 +00:00
James Turner 03bdad0a10 Try to capture cause of BTG load failures 2020-11-04 11:52:58 +00:00
James Turner 537776e1f8 TerraSync: fix local-file copying
Avoid downloading data corresponding to files shipped in FGData.
2020-11-04 11:22:48 +00:00
James Turner f34a4a304e Untar: log error details when output create fails
Trying to understand why writing to the output files fails for some
users.

Sentry-Id: FLIGHTGEAR-SS
2020-11-03 21:26:03 +00:00
James Turner a59c4e2c8b TerraSync: make tarball extraction asynchronous 2020-11-03 17:49:19 +00:00
Julian Smith 46f4967f6e Allow use of old zlib-1.2.3 on OpenBSD.
As of 2020-08-01, OpenBSD's system zlib is 1.2.3 which doesn't
have gzoffset(). However we can get away with this by making
gzfilebuf::approxOffset() always return zero.
2020-11-01 11:12:00 +00:00
James Turner 9305417207 Terrasync: tarball extraction, use larger buffer
Use a 1MB buffer, 2kByte is very 1979 :)
2020-11-01 11:11:31 +00:00
James Turner 11da8b33f9 TerraSync: switch to per-directory hash caching
Avoid a central hash cache becoming enormous, now we use the selective
download scheme for the tile dirs.

Hash name is changed to co-exist with older versions.
2020-11-01 11:11:27 +00:00
James Turner c7b320eb55 Fix Airports/ initial sync 2020-11-01 11:11:23 +00:00
James Turner 72b2eb0ebf TerraSync: avoid 404s to probe missing tiles
Use the top-level dirIndex files to determine if a 1x1 tile dir
exists, instead of proving the server via a 404. This reduces the
number of requests we make considerably, which is … important.
2020-11-01 11:11:19 +00:00
James Turner ec3829addb TerraSync: validate local dirs incrementally
Add a process() method to HTTPRepository, and use this to
incrementally validate subdirs after the .dirIndex is received. This 
avoids large pauses of the TerraSync thread, when all of Airports/
is validated at once.
2020-11-01 11:11:15 +00:00
James Turner 96bafef3f3 TerraSync: use an unordered_map for the hash cache
Linear-scan is a bit slow in debug builds, for the large Airports/ tree;
switch to an unordered_map.

Will back-port to the LTS once tested a bit more.
2020-11-01 11:11:12 +00:00
James Turner 3ff3bd0a6c Props: allow flushing the atomic change listener
Trying to narrow down causes of the ‘unregister listeners crashes on
shutdown’ reports.
2020-11-01 11:11:07 +00:00
Automatic Release Builder 78d073a0f0 new version: 2020.3.1 2020-10-26 09:09:22 +00:00
Scott Giese d62796c19d METAR: When Wind unit not specified, default to knots.
FLIGHTGEAR-F6 resolved
2020-10-26 08:58:06 +00:00
Scott Giese 7ea7ff43fc METAR Wind Sensor Failure.
FLIGHTGEAR-F9 resolved.
2020-10-26 08:57:24 +00:00
James Turner dafd185595 Fix for crash reported by Michael Danilov
In case uDNS returns a NULL txt pointer, don’t try to create a
std::string from it, since this will crash,

See: https://sourceforge.net/p/flightgear/codetickets/2398/
2020-10-26 08:54:18 +00:00
Automatic Release Builder 1568ed8b97 Catalog migration: migrate packages too
When doing a catalog migration to a new ID (eg, 2018 -> 2020), also
mark the installed packages for installation, on the new catalog.

Related to this, when manually removing a catalog, record this fact,
so we don’t re-add it automatically due to migration.

Add unit-tests covering both of these cases.
2020-10-22 21:27:47 +01:00
Automatic Release Builder 444e2ffb2d Sound: readWAV: avoid common exceptions.
Avoid exceptions for the common ‘file not found’ case, and instead
return false / nullptr. Erik says it’s fine.
2020-10-15 17:00:30 +01:00
Automatic Release Builder 32ccdaec6f Update version file 2020-10-13 22:27:38 +01:00
Automatic Release Builder bd9f04d980 TerraSync: retry after socket failures
Assume socket failures are intermittent, up to some maximum count
(currently configured as 16). Add a test case to cover this.
2020-10-13 11:38:35 +01:00
Automatic Release Builder 9c530d6978 TerraSync: Rate-limit hash-cache writes
This helps with IO-limited performance on Windows
2020-10-07 12:23:33 +01:00
Automatic Release Builder 05094510be SGPath: optimise exists() on Windows
For a very common existence only check, use a dedicated win32 API
function, to save some time.
2020-10-07 12:21:49 +01:00
Automatic Release Builder 41e43eeba0 TerraSync: fix Windows behaviour
SGDir on Windows returns dot-files by default, which includes our
hash-cache, causing it to be orphaned.
2020-10-06 23:51:06 +01:00
Automatic Release Builder f95cbd703a Add a hasInstance to ResourceManager
This is needed to allow order-independent shutdown, due to the dumb
ownership behaviour for providers.
2020-10-06 11:34:03 +01:00
Automatic Release Builder e39036a635 SGFile::computeHash: check malloc result
Trying to fix a reported crash, in this code. malloc() returning null
seems unlikely but worth checking for. ALso use a unique_ptr with
a custom deleter to avoid a leak if we throw.

Sentry-Id: FLIGHTGEAR-3Y
2020-10-06 11:33:17 +01:00
Automatic Release Builder 1171d57b72 XMLSound: Avoid using exceptions for missing file
Reducing how noisy our exception reporting is, by using simple return
value from SGXMLSound::init.
2020-10-05 14:21:37 +01:00
Automatic Release Builder 852058150b TerraSync: fast start Airports/ sync
If TerraSync/Airports is missing, download Airport_archive.tgz instead.
In all other cases update as normal. This has the effect, that fresh
installs make many fewer requests to TerraSync servers.
2020-10-05 11:58:03 +01:00
45 changed files with 2160 additions and 778 deletions
+8 -1
View File
@@ -278,7 +278,14 @@ else()
endif() endif()
endif(SIMGEAR_HEADLESS) endif(SIMGEAR_HEADLESS)
find_package(ZLIB 1.2.4 REQUIRED) if(${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD")
# As of 2020-08-01, OpenBSD's system zlib is slightly old, but it's usable
# with a workaround in simgear/io/iostreams/gzfstream.cxx.
find_package(ZLIB 1.2.3 REQUIRED)
else()
find_package(ZLIB 1.2.4 REQUIRED)
endif()
find_package(CURL REQUIRED) find_package(CURL REQUIRED)
if (SYSTEM_EXPAT) if (SYSTEM_EXPAT)
+1 -1
View File
@@ -1 +1 @@
2020.2.1 2020.3.4
+2 -1
View File
@@ -167,7 +167,8 @@ target_link_libraries(SimGearCore PRIVATE
${CMAKE_THREAD_LIBS_INIT} ${CMAKE_THREAD_LIBS_INIT}
${COCOA_LIBRARY} ${COCOA_LIBRARY}
${CURL_LIBRARIES} ${CURL_LIBRARIES}
${WINSOCK_LIBRARY}) ${WINSOCK_LIBRARY}
${SHLWAPI_LIBRARY})
if(SYSTEM_EXPAT) if(SYSTEM_EXPAT)
target_link_libraries(SimGearCore PRIVATE ${EXPAT_LIBRARIES}) target_link_libraries(SimGearCore PRIVATE ${EXPAT_LIBRARIES})
+3
View File
@@ -218,6 +218,9 @@ const float SG_RADIANS_TO_DEGREES = 180.0f / SG_PI;
#define SG_OBJECT_RANGE_ROUGH 9000.0 #define SG_OBJECT_RANGE_ROUGH 9000.0
#define SG_OBJECT_RANGE_DETAILED 1500.0 #define SG_OBJECT_RANGE_DETAILED 1500.0
/** Minimum expiry time of PagedLOD within the Tile. Overridden by /sim/rendering/plod-minimum-expiry-time-secs **/
#define SG_TILE_MIN_EXPIRY 180.0
/** Radius of scenery tiles in m **/ /** Radius of scenery tiles in m **/
#define SG_TILE_RADIUS 14000.0 #define SG_TILE_RADIUS 14000.0
+28 -7
View File
@@ -631,30 +631,41 @@ bool SGMetar::scanWind()
int dir; int dir;
if (!strncmp(m, "VRB", 3)) if (!strncmp(m, "VRB", 3))
m += 3, dir = -1; m += 3, dir = -1;
else if (!strncmp(m, "///", 3)) // direction not measurable
m += 3, dir = -1;
else if (!scanNumber(&m, &dir, 3)) else if (!scanNumber(&m, &dir, 3))
return false; return false;
int i; int i;
if (!scanNumber(&m, &i, 2, 3)) if (!strncmp(m, "//", 2)) // speed not measurable
m += 2, i = -1;
else if (!scanNumber(&m, &i, 2, 3))
return false; return false;
double speed = i; double speed = i;
double gust = NaN; double gust = NaN;
if (*m == 'G') { if (*m == 'G') {
m++; m++;
if (!scanNumber(&m, &i, 2, 3)) if (!strncmp(m, "//", 2)) // speed not measurable
m += 2, i = -1;
else if (!scanNumber(&m, &i, 2, 3))
return false; return false;
gust = i;
if (i != -1)
gust = i;
} }
double factor; double factor;
if (!strncmp(m, "KT", 2)) if (!strncmp(m, "KT", 2))
m += 2, factor = SG_KT_TO_MPS; m += 2, factor = SG_KT_TO_MPS;
else if (!strncmp(m, "KMH", 3)) else if (!strncmp(m, "KMH", 3)) // invalid Km/h
m += 3, factor = SG_KMH_TO_MPS; m += 3, factor = SG_KMH_TO_MPS;
else if (!strncmp(m, "KPH", 3)) // ?? else if (!strncmp(m, "KPH", 3)) // invalid Km/h
m += 3, factor = SG_KMH_TO_MPS; m += 3, factor = SG_KMH_TO_MPS;
else if (!strncmp(m, "MPS", 3)) else if (!strncmp(m, "MPS", 3))
m += 3, factor = 1.0; m += 3, factor = 1.0;
else if (!strncmp(m, " ", 1)) // default to Knots
factor = SG_KT_TO_MPS;
else else
return false; return false;
if (!scanBoundary(&m)) if (!scanBoundary(&m))
@@ -674,18 +685,28 @@ bool SGMetar::scanVariability()
{ {
char *m = _m; char *m = _m;
int from, to; int from, to;
if (!scanNumber(&m, &from, 3))
if (!strncmp(m, "///", 3)) // direction not measurable
m += 3, from = -1;
else if (!scanNumber(&m, &from, 3))
return false; return false;
if (*m++ != 'V') if (*m++ != 'V')
return false; return false;
if (!scanNumber(&m, &to, 3))
if (!strncmp(m, "///", 3)) // direction not measurable
m += 3, to = -1;
else if (!scanNumber(&m, &to, 3))
return false; return false;
if (!scanBoundary(&m)) if (!scanBoundary(&m))
return false; return false;
_m = m; _m = m;
_wind_range_from = from; _wind_range_from = from;
_wind_range_to = to; _wind_range_to = to;
_grpcount++; _grpcount++;
return true; return true;
} }
+26
View File
@@ -76,12 +76,38 @@ void test_sensor_failure_cloud()
SG_CHECK_EQUAL_EP2(m1.getPressure_hPa(), 1025, TEST_EPSILON); SG_CHECK_EQUAL_EP2(m1.getPressure_hPa(), 1025, TEST_EPSILON);
} }
void test_sensor_failure_wind()
{
SGMetar m1("2020/10/23 16:55 LIVD 231655Z /////KT 9999 OVC025 10/08 Q1020 RMK OVC VIS MIN 9999 BLU");
SG_CHECK_EQUAL(m1.getWindDir(), -1);
SG_CHECK_EQUAL_EP2(m1.getWindSpeed_kt(), -1, TEST_EPSILON);
SGMetar m2("2020/10/21 16:55 LIVD 211655Z /////KT CAVOK 07/03 Q1023 RMK SKC VIS MIN 9999 BLU");
SG_CHECK_EQUAL(m2.getWindDir(), -1);
SG_CHECK_EQUAL_EP2(m2.getWindSpeed_kt(), -1, TEST_EPSILON);
SGMetar m3("2020/11/17 16:00 CYAZ 171600Z 14040G//KT 10SM -RA OVC012 12/11 A2895 RMK NS8 VIA CYXY SLP806 DENSITY ALT 900FT");
SG_CHECK_EQUAL(m3.getWindDir(), 140);
SG_CHECK_EQUAL_EP2(m3.getWindSpeed_kt(), 40, TEST_EPSILON);
SG_CHECK_EQUAL_EP2(m3.getGustSpeed_kt(), SGMetarNaN, TEST_EPSILON);
}
void test_wind_unit_not_specified()
{
SGMetar m1("2020/10/23 11:58 KLSV 231158Z 05010G14 10SM CLR 16/M04 A2992 RMK SLPNO WND DATA ESTMD ALSTG/SLP ESTMD 10320 20124 5//// $");
SG_CHECK_EQUAL(m1.getWindDir(), 50);
SG_CHECK_EQUAL_EP2(m1.getWindSpeed_kt(), 10.0, TEST_EPSILON);
SG_CHECK_EQUAL_EP2(m1.getGustSpeed_kt(), 14.0, TEST_EPSILON);
}
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
try { try {
test_basic(); test_basic();
test_sensor_failure_weather(); test_sensor_failure_weather();
test_sensor_failure_cloud(); test_sensor_failure_cloud();
test_sensor_failure_wind();
test_wind_unit_not_specified();
} catch (sg_exception& e) { } catch (sg_exception& e) {
cerr << "got exception:" << e.getMessage() << endl; cerr << "got exception:" << e.getMessage() << endl;
return -1; return -1;
+3
View File
@@ -35,10 +35,12 @@ set(SOURCES
sg_socket.cxx sg_socket.cxx
sg_socket_udp.cxx sg_socket_udp.cxx
HTTPClient.cxx HTTPClient.cxx
HTTPTestApi_private.hxx
HTTPFileRequest.cxx HTTPFileRequest.cxx
HTTPMemoryRequest.cxx HTTPMemoryRequest.cxx
HTTPRequest.cxx HTTPRequest.cxx
HTTPRepository.cxx HTTPRepository.cxx
HTTPRepository_private.hxx
untar.cxx untar.cxx
) )
@@ -81,6 +83,7 @@ add_test(binobj ${EXECUTABLE_OUTPUT_PATH}/test_binobj)
add_executable(test_repository test_repository.cxx) add_executable(test_repository test_repository.cxx)
target_link_libraries(test_repository ${TEST_LIBS}) target_link_libraries(test_repository ${TEST_LIBS})
target_compile_definitions(test_repository PUBLIC BUILDING_TESTSUITE)
add_test(http_repository ${EXECUTABLE_OUTPUT_PATH}/test_repository) add_test(http_repository ${EXECUTABLE_OUTPUT_PATH}/test_repository)
add_executable(test_untar test_untar.cxx) add_executable(test_untar test_untar.cxx)
+86 -43
View File
@@ -61,6 +61,10 @@ public:
struct dns_ctx * ctx; struct dns_ctx * ctx;
static size_t instanceCounter; static size_t instanceCounter;
using RequestVec = std::vector<Request_ptr>;
RequestVec _activeRequests;
}; };
size_t Client::ClientPrivate::instanceCounter = 0; size_t Client::ClientPrivate::instanceCounter = 0;
@@ -78,6 +82,11 @@ Request::~Request()
{ {
} }
void Request::cancel()
{
_cancelled = true;
}
bool Request::isTimeout() const bool Request::isTimeout() const
{ {
return (time(NULL) - _start) > _timeout_secs; return (time(NULL) - _start) > _timeout_secs;
@@ -114,18 +123,20 @@ static void dnscbSRV(struct dns_ctx *ctx, struct dns_rr_srv *result, void *data)
{ {
SRVRequest * r = static_cast<SRVRequest*>(data); SRVRequest * r = static_cast<SRVRequest*>(data);
if (result) { if (result) {
r->cname = result->dnssrv_cname; if (!r->isCancelled()) {
r->qname = result->dnssrv_qname; r->cname = result->dnssrv_cname;
r->ttl = result->dnssrv_ttl; r->qname = result->dnssrv_qname;
for (int i = 0; i < result->dnssrv_nrr; i++) { r->ttl = result->dnssrv_ttl;
SRVRequest::SRV_ptr srv(new SRVRequest::SRV); for (int i = 0; i < result->dnssrv_nrr; i++) {
r->entries.push_back(srv); SRVRequest::SRV_ptr srv(new SRVRequest::SRV);
srv->priority = result->dnssrv_srv[i].priority; r->entries.push_back(srv);
srv->weight = result->dnssrv_srv[i].weight; srv->priority = result->dnssrv_srv[i].priority;
srv->port = result->dnssrv_srv[i].port; srv->weight = result->dnssrv_srv[i].weight;
srv->target = result->dnssrv_srv[i].name; srv->port = result->dnssrv_srv[i].port;
srv->target = result->dnssrv_srv[i].name;
}
std::sort(r->entries.begin(), r->entries.end(), sortSRV);
} }
std::sort( r->entries.begin(), r->entries.end(), sortSRV );
free(result); free(result);
} }
r->setComplete(); r->setComplete();
@@ -134,11 +145,16 @@ static void dnscbSRV(struct dns_ctx *ctx, struct dns_rr_srv *result, void *data)
void SRVRequest::submit( Client * client ) void SRVRequest::submit( Client * client )
{ {
// if service is defined, pass service and protocol // if service is defined, pass service and protocol
if (!dns_submit_srv(client->d->ctx, getDn().c_str(), _service.empty() ? NULL : _service.c_str(), _service.empty() ? NULL : _protocol.c_str(), 0, dnscbSRV, this )) { auto q = dns_submit_srv(client->d->ctx, getDn().c_str(), _service.empty() ? NULL : _service.c_str(),
_service.empty() ? NULL : _protocol.c_str(),
0, dnscbSRV, this);
if (!q) {
SG_LOG(SG_IO, SG_ALERT, "Can't submit dns request for " << getDn()); SG_LOG(SG_IO, SG_ALERT, "Can't submit dns request for " << getDn());
return; return;
} }
_start = time(NULL); _start = time(NULL);
_query = q;
} }
TXTRequest::TXTRequest( const std::string & dn ) : TXTRequest::TXTRequest( const std::string & dn ) :
@@ -151,17 +167,24 @@ static void dnscbTXT(struct dns_ctx *ctx, struct dns_rr_txt *result, void *data)
{ {
TXTRequest * r = static_cast<TXTRequest*>(data); TXTRequest * r = static_cast<TXTRequest*>(data);
if (result) { if (result) {
r->cname = result->dnstxt_cname; if (!r->isCancelled()) {
r->qname = result->dnstxt_qname; r->cname = result->dnstxt_cname;
r->ttl = result->dnstxt_ttl; r->qname = result->dnstxt_qname;
for (int i = 0; i < result->dnstxt_nrr; i++) { r->ttl = result->dnstxt_ttl;
//TODO: interprete the .len field of dnstxt_txt? for (int i = 0; i < result->dnstxt_nrr; i++) {
string txt = string((char*)result->dnstxt_txt[i].txt); //TODO: interprete the .len field of dnstxt_txt?
r->entries.push_back( txt ); auto rawTxt = reinterpret_cast<char*>(result->dnstxt_txt[i].txt);
string_list tokens = simgear::strutils::split( txt, "=", 1 ); if (!rawTxt) {
if( tokens.size() == 2 ) { continue;
r->attributes[tokens[0]] = tokens[1]; }
}
const string txt{rawTxt};
r->entries.push_back(txt);
string_list tokens = simgear::strutils::split(txt, "=", 1);
if (tokens.size() == 2) {
r->attributes[tokens[0]] = tokens[1];
}
}
} }
free(result); free(result);
} }
@@ -171,11 +194,13 @@ static void dnscbTXT(struct dns_ctx *ctx, struct dns_rr_txt *result, void *data)
void TXTRequest::submit( Client * client ) void TXTRequest::submit( Client * client )
{ {
// protocol and service an already encoded in DN so pass in NULL for both // protocol and service an already encoded in DN so pass in NULL for both
if (!dns_submit_txt(client->d->ctx, getDn().c_str(), DNS_C_IN, 0, dnscbTXT, this )) { auto q = dns_submit_txt(client->d->ctx, getDn().c_str(), DNS_C_IN, 0, dnscbTXT, this);
if (!q) {
SG_LOG(SG_IO, SG_ALERT, "Can't submit dns request for " << getDn()); SG_LOG(SG_IO, SG_ALERT, "Can't submit dns request for " << getDn());
return; return;
} }
_start = time(NULL); _start = time(NULL);
_query = q;
} }
@@ -190,27 +215,29 @@ static void dnscbNAPTR(struct dns_ctx *ctx, struct dns_rr_naptr *result, void *d
{ {
NAPTRRequest * r = static_cast<NAPTRRequest*>(data); NAPTRRequest * r = static_cast<NAPTRRequest*>(data);
if (result) { if (result) {
r->cname = result->dnsnaptr_cname; if (!r->isCancelled()) {
r->qname = result->dnsnaptr_qname; r->cname = result->dnsnaptr_cname;
r->ttl = result->dnsnaptr_ttl; r->qname = result->dnsnaptr_qname;
for (int i = 0; i < result->dnsnaptr_nrr; i++) { r->ttl = result->dnsnaptr_ttl;
if( !r->qservice.empty() && r->qservice != result->dnsnaptr_naptr[i].service ) for (int i = 0; i < result->dnsnaptr_nrr; i++) {
continue; if (!r->qservice.empty() && r->qservice != result->dnsnaptr_naptr[i].service)
continue;
//TODO: case ignore and result flags may have more than one flag //TODO: case ignore and result flags may have more than one flag
if( !r->qflags.empty() && r->qflags != result->dnsnaptr_naptr[i].flags ) if (!r->qflags.empty() && r->qflags != result->dnsnaptr_naptr[i].flags)
continue; continue;
NAPTRRequest::NAPTR_ptr naptr(new NAPTRRequest::NAPTR); NAPTRRequest::NAPTR_ptr naptr(new NAPTRRequest::NAPTR);
r->entries.push_back(naptr); r->entries.push_back(naptr);
naptr->order = result->dnsnaptr_naptr[i].order; naptr->order = result->dnsnaptr_naptr[i].order;
naptr->preference = result->dnsnaptr_naptr[i].preference; naptr->preference = result->dnsnaptr_naptr[i].preference;
naptr->flags = result->dnsnaptr_naptr[i].flags; naptr->flags = result->dnsnaptr_naptr[i].flags;
naptr->service = result->dnsnaptr_naptr[i].service; naptr->service = result->dnsnaptr_naptr[i].service;
naptr->regexp = result->dnsnaptr_naptr[i].regexp; naptr->regexp = result->dnsnaptr_naptr[i].regexp;
naptr->replacement = result->dnsnaptr_naptr[i].replacement; naptr->replacement = result->dnsnaptr_naptr[i].replacement;
}
std::sort(r->entries.begin(), r->entries.end(), sortNAPTR);
} }
std::sort( r->entries.begin(), r->entries.end(), sortNAPTR );
free(result); free(result);
} }
r->setComplete(); r->setComplete();
@@ -218,11 +245,13 @@ static void dnscbNAPTR(struct dns_ctx *ctx, struct dns_rr_naptr *result, void *d
void NAPTRRequest::submit( Client * client ) void NAPTRRequest::submit( Client * client )
{ {
if (!dns_submit_naptr(client->d->ctx, getDn().c_str(), 0, dnscbNAPTR, this )) { auto q = dns_submit_naptr(client->d->ctx, getDn().c_str(), 0, dnscbNAPTR, this);
if (!q) {
SG_LOG(SG_IO, SG_ALERT, "Can't submit dns request for " << getDn()); SG_LOG(SG_IO, SG_ALERT, "Can't submit dns request for " << getDn());
return; return;
} }
_start = time(NULL); _start = time(NULL);
_query = q;
} }
@@ -237,6 +266,7 @@ Client::Client() :
void Client::makeRequest(const Request_ptr& r) void Client::makeRequest(const Request_ptr& r)
{ {
d->_activeRequests.push_back(r);
r->submit(this); r->submit(this);
} }
@@ -247,6 +277,19 @@ void Client::update(int waitTimeout)
return; return;
dns_ioevent(d->ctx, now); dns_ioevent(d->ctx, now);
// drop our owning ref to completed requests,
// and cancel any which timed out
auto it = std::remove_if(d->_activeRequests.begin(), d->_activeRequests.end(),
[this](const Request_ptr& r) {
if (r->isTimeout()) {
dns_cancel(d->ctx, reinterpret_cast<struct dns_query*>(r->_query));
return true;
}
return r->isComplete();
});
d->_activeRequests.erase(it, d->_activeRequests.end());
} }
} // of namespace DNS } // of namespace DNS
+14 -4
View File
@@ -40,28 +40,38 @@ namespace DNS
{ {
class Client; class Client;
using UDNSQueryPtr = void*;
class Request : public SGReferenced class Request : public SGReferenced
{ {
public: public:
Request( const std::string & dn ); Request( const std::string & dn );
virtual ~Request(); virtual ~Request();
std::string getDn() const { return _dn; } const std::string& getDn() const { return _dn; }
int getType() const { return _type; } int getType() const { return _type; }
bool isComplete() const { return _complete; } bool isComplete() const { return _complete; }
bool isTimeout() const; bool isTimeout() const;
void setComplete( bool b = true ) { _complete = b; } void setComplete( bool b = true ) { _complete = b; }
bool isCancelled() const { return _cancelled; }
virtual void submit( Client * client) = 0; virtual void submit( Client * client) = 0;
void cancel();
std::string cname; std::string cname;
std::string qname; std::string qname;
unsigned ttl; unsigned ttl;
protected: protected:
friend class Client;
UDNSQueryPtr _query = nullptr;
std::string _dn; std::string _dn;
int _type; int _type;
bool _complete; bool _complete;
time_t _timeout_secs; time_t _timeout_secs;
time_t _start; time_t _start;
bool _cancelled = false;
}; };
typedef SGSharedPtr<Request> Request_ptr; typedef SGSharedPtr<Request> Request_ptr;
@@ -69,7 +79,7 @@ class NAPTRRequest : public Request
{ {
public: public:
NAPTRRequest( const std::string & dn ); NAPTRRequest( const std::string & dn );
virtual void submit( Client * client ); void submit(Client* client) override;
struct NAPTR : SGReferenced { struct NAPTR : SGReferenced {
int order; int order;
@@ -92,7 +102,7 @@ class SRVRequest : public Request
public: public:
SRVRequest( const std::string & dn ); SRVRequest( const std::string & dn );
SRVRequest( const std::string & dn, const string & service, const string & protocol ); SRVRequest( const std::string & dn, const string & service, const string & protocol );
virtual void submit( Client * client ); void submit(Client* client) override;
struct SRV : SGReferenced { struct SRV : SGReferenced {
int priority; int priority;
@@ -112,7 +122,7 @@ class TXTRequest : public Request
{ {
public: public:
TXTRequest( const std::string & dn ); TXTRequest( const std::string & dn );
virtual void submit( Client * client ); void submit(Client* client) override;
typedef std::vector<string> TXT_list; typedef std::vector<string> TXT_list;
typedef std::map<std::string,std::string> TXT_Attribute_map; typedef std::map<std::string,std::string> TXT_Attribute_map;
+43 -49
View File
@@ -37,7 +37,6 @@
#include <simgear/simgear_config.h> #include <simgear/simgear_config.h>
#include <curl/multi.h>
#include <simgear/io/sg_netChat.hxx> #include <simgear/io/sg_netChat.hxx>
@@ -47,6 +46,9 @@
#include <simgear/timing/timestamp.hxx> #include <simgear/timing/timestamp.hxx>
#include <simgear/structure/exception.hxx> #include <simgear/structure/exception.hxx>
#include "HTTPClient_private.hxx"
#include "HTTPTestApi_private.hxx"
#if defined( HAVE_VERSION_H ) && HAVE_VERSION_H #if defined( HAVE_VERSION_H ) && HAVE_VERSION_H
#include "version.h" #include "version.h"
#else #else
@@ -64,50 +66,20 @@ namespace HTTP
extern const int DEFAULT_HTTP_PORT = 80; extern const int DEFAULT_HTTP_PORT = 80;
const char* CONTENT_TYPE_URL_ENCODED = "application/x-www-form-urlencoded"; const char* CONTENT_TYPE_URL_ENCODED = "application/x-www-form-urlencoded";
class Connection; void Client::ClientPrivate::createCurlMulti() {
typedef std::multimap<std::string, Connection*> ConnectionDict; curlMulti = curl_multi_init();
typedef std::list<Request_ptr> RequestList; // see https://curl.haxx.se/libcurl/c/CURLMOPT_PIPELINING.html
// we request HTTP 1.1 pipelining
class Client::ClientPrivate curl_multi_setopt(curlMulti, CURLMOPT_PIPELINING, 1 /* aka CURLPIPE_HTTP1 */);
{
public:
CURLM* curlMulti;
void createCurlMulti()
{
curlMulti = curl_multi_init();
// see https://curl.haxx.se/libcurl/c/CURLMOPT_PIPELINING.html
// we request HTTP 1.1 pipelining
curl_multi_setopt(curlMulti, CURLMOPT_PIPELINING, 1 /* aka CURLPIPE_HTTP1 */);
#if (LIBCURL_VERSION_MINOR >= 30) #if (LIBCURL_VERSION_MINOR >= 30)
curl_multi_setopt(curlMulti, CURLMOPT_MAX_TOTAL_CONNECTIONS, (long) maxConnections); curl_multi_setopt(curlMulti, CURLMOPT_MAX_TOTAL_CONNECTIONS,
curl_multi_setopt(curlMulti, CURLMOPT_MAX_PIPELINE_LENGTH, (long)maxConnections);
(long) maxPipelineDepth); curl_multi_setopt(curlMulti, CURLMOPT_MAX_PIPELINE_LENGTH,
curl_multi_setopt(curlMulti, CURLMOPT_MAX_HOST_CONNECTIONS, (long)maxPipelineDepth);
(long) maxHostConnections); curl_multi_setopt(curlMulti, CURLMOPT_MAX_HOST_CONNECTIONS,
(long)maxHostConnections);
#endif #endif
} }
typedef std::map<Request_ptr, CURL*> RequestCurlMap;
RequestCurlMap requests;
std::string userAgent;
std::string proxy;
int proxyPort;
std::string proxyAuth;
unsigned int maxConnections;
unsigned int maxHostConnections;
unsigned int maxPipelineDepth;
RequestList pendingRequests;
SGTimeStamp timeTransferSample;
unsigned int bytesTransferred;
unsigned int lastTransferRate;
uint64_t totalBytesDownloaded;
SGPath tlsCertificatePath;
};
Client::Client() : Client::Client() :
d(new ClientPrivate) d(new ClientPrivate)
@@ -223,12 +195,23 @@ void Client::update(int waitTimeout)
assert(it->second == e); assert(it->second == e);
d->requests.erase(it); d->requests.erase(it);
if (msg->data.result == 0) { bool doProcess = true;
req->responseComplete(); if (d->testsuiteResponseDoneCallback) {
} else { doProcess =
SG_LOG(SG_IO, SG_WARN, "CURL Result:" << msg->data.result << " " << curl_easy_strerror(msg->data.result)); !d->testsuiteResponseDoneCallback(msg->data.result, req);
req->setFailure(msg->data.result, curl_easy_strerror(msg->data.result)); }
}
if (doProcess) {
if (msg->data.result == 0) {
req->responseComplete();
} else {
SG_LOG(SG_IO, SG_WARN,
"CURL Result:" << msg->data.result << " "
<< curl_easy_strerror(msg->data.result));
req->setFailure(msg->data.result,
curl_easy_strerror(msg->data.result));
}
}
curl_multi_remove_handle(d->curlMulti, e); curl_multi_remove_handle(d->curlMulti, e);
curl_easy_cleanup(e); curl_easy_cleanup(e);
@@ -559,6 +542,17 @@ void Client::clearAllConnections()
d->createCurlMulti(); d->createCurlMulti();
} }
/////////////////////////////////////////////////////////////////////
void TestApi::setResponseDoneCallback(Client *cl, ResponseDoneCallback cb) {
cl->d->testsuiteResponseDoneCallback = cb;
}
void TestApi::markRequestAsFailed(Request_ptr req, int curlCode,
const std::string &message) {
req->setFailure(curlCode, message);
}
} // of namespace HTTP } // of namespace HTTP
} // of namespace simgear } // of namespace simgear
+3 -1
View File
@@ -24,7 +24,8 @@
#ifndef SG_HTTP_CLIENT_HXX #ifndef SG_HTTP_CLIENT_HXX
#define SG_HTTP_CLIENT_HXX #define SG_HTTP_CLIENT_HXX
#include <memory> // for std::unique_ptr #include <functional>
#include <memory> // for std::unique_ptr
#include <stdint.h> // for uint_64t #include <stdint.h> // for uint_64t
#include <simgear/io/HTTPFileRequest.hxx> #include <simgear/io/HTTPFileRequest.hxx>
@@ -125,6 +126,7 @@ private:
friend class Connection; friend class Connection;
friend class Request; friend class Request;
friend class TestApi;
class ClientPrivate; class ClientPrivate;
std::unique_ptr<ClientPrivate> d; std::unique_ptr<ClientPrivate> d;
+68
View File
@@ -0,0 +1,68 @@
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#pragma once
#include <list>
#include <map>
#include "HTTPClient.hxx"
#include "HTTPRequest.hxx"
#include <simgear/timing/timestamp.hxx>
#include <curl/multi.h>
namespace simgear {
namespace HTTP {
typedef std::list<Request_ptr> RequestList;
using ResponseDoneCallback =
std::function<bool(int curlResult, Request_ptr req)>;
class Client::ClientPrivate {
public:
CURLM *curlMulti;
void createCurlMulti();
typedef std::map<Request_ptr, CURL *> RequestCurlMap;
RequestCurlMap requests;
std::string userAgent;
std::string proxy;
int proxyPort;
std::string proxyAuth;
unsigned int maxConnections;
unsigned int maxHostConnections;
unsigned int maxPipelineDepth;
RequestList pendingRequests;
SGTimeStamp timeTransferSample;
unsigned int bytesTransferred;
unsigned int lastTransferRate;
uint64_t totalBytesDownloaded;
SGPath tlsCertificatePath;
// only used by unit-tests / test-api, but
// only costs us a pointe here to declare it.
ResponseDoneCallback testsuiteResponseDoneCallback;
};
} // namespace HTTP
} // namespace simgear
File diff suppressed because it is too large Load Diff
+72 -37
View File
@@ -20,6 +20,7 @@
#ifndef SG_IO_HTTP_REPOSITORY_HXX #ifndef SG_IO_HTTP_REPOSITORY_HXX
#define SG_IO_HTTP_REPOSITORY_HXX #define SG_IO_HTTP_REPOSITORY_HXX
#include <functional>
#include <memory> #include <memory>
#include <simgear/misc/sg_path.hxx> #include <simgear/misc/sg_path.hxx>
@@ -32,49 +33,83 @@ class HTTPRepoPrivate;
class HTTPRepository class HTTPRepository
{ {
public: public:
enum ResultCode { enum ResultCode {
REPO_NO_ERROR = 0, REPO_NO_ERROR = 0,
REPO_ERROR_NOT_FOUND, REPO_ERROR_NOT_FOUND,
REPO_ERROR_SOCKET, REPO_ERROR_SOCKET,
SVN_ERROR_XML, SVN_ERROR_XML,
SVN_ERROR_TXDELTA, SVN_ERROR_TXDELTA,
REPO_ERROR_IO, REPO_ERROR_IO,
REPO_ERROR_CHECKSUM, REPO_ERROR_CHECKSUM,
REPO_ERROR_FILE_NOT_FOUND, REPO_ERROR_FILE_NOT_FOUND,
REPO_ERROR_HTTP, REPO_ERROR_HTTP,
REPO_ERROR_CANCELLED, REPO_ERROR_CANCELLED,
REPO_PARTIAL_UPDATE REPO_PARTIAL_UPDATE ///< repository is working, but file-level failures
///< occurred
};
HTTPRepository(const SGPath &root, HTTP::Client *cl);
virtual ~HTTPRepository();
virtual SGPath fsBase() const;
virtual void setBaseUrl(const std::string &url);
virtual std::string baseUrl() const;
virtual HTTP::Client *http() const;
virtual void update();
virtual bool isDoingSync() const;
/**
@brief call this periodically to progress non-network tasks
*/
void process();
virtual ResultCode failure() const;
virtual size_t bytesToDownload() const;
virtual size_t bytesDownloaded() const;
/**
* optionally provide the location of an installer copy of this
* repository. When a file is missing it will be copied from this tree.
*/
void setInstalledCopyPath(const SGPath &copyPath);
static std::string resultCodeAsString(ResultCode code);
enum class SyncAction { Add, Update, Delete, UpToDate };
enum EntryType { FileType, DirectoryType, TarballType };
struct SyncItem {
const std::string directory; // relative path in the repository
const EntryType type;
const std::string filename;
const SyncAction action;
const SGPath pathOnDisk; // path the entry does / will have
};
using SyncPredicate = std::function<bool(const SyncItem &item)>;
void setFilter(SyncPredicate sp);
struct Failure {
SGPath path;
ResultCode error;
}; };
HTTPRepository(const SGPath& root, HTTP::Client* cl); using FailureVec = std::vector<Failure>;
virtual ~HTTPRepository();
virtual SGPath fsBase() const;
virtual void setBaseUrl(const std::string& url);
virtual std::string baseUrl() const;
virtual HTTP::Client* http() const;
virtual void update();
virtual bool isDoingSync() const;
virtual ResultCode failure() const;
virtual size_t bytesToDownload() const;
virtual size_t bytesDownloaded() const;
/** /**
* optionally provide the location of an installer copy of this * @brief return file-level failures
* repository. When a file is missing it will be copied from this tree.
*/ */
void setInstalledCopyPath(const SGPath& copyPath); FailureVec failures() const;
static std::string resultCodeAsString(ResultCode code); private:
private:
bool isBare() const; bool isBare() const;
std::unique_ptr<HTTPRepoPrivate> _d; std::unique_ptr<HTTPRepoPrivate> _d;
+126
View File
@@ -0,0 +1,126 @@
// HTTPRepository.cxx -- plain HTTP TerraSync remote client
//
// Copyright (C) 20126 James Turner <zakalawe@mac.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
// USA.
#pragma once
#include <deque>
#include <functional>
#include <memory>
#include <string>
#include <unordered_map>
#include <simgear/io/HTTPClient.hxx>
#include <simgear/misc/sg_path.hxx>
#include "HTTPRepository.hxx"
namespace simgear {
class HTTPDirectory;
using HTTPDirectory_ptr = std::unique_ptr<HTTPDirectory>;
class HTTPRepoGetRequest : public HTTP::Request {
public:
HTTPRepoGetRequest(HTTPDirectory *d, const std::string &u)
: HTTP::Request(u), _directory(d) {}
virtual void cancel();
size_t contentSize() const { return _contentSize; }
void setContentSize(size_t sz) { _contentSize = sz; }
protected:
HTTPDirectory *_directory;
size_t _contentSize = 0;
};
using RepoRequestPtr = SGSharedPtr<HTTPRepoGetRequest>;
class HTTPRepoPrivate {
public:
HTTPRepository::FailureVec failures;
int maxPermittedFailures = 16;
HTTPRepoPrivate(HTTPRepository *parent)
: p(parent), isUpdating(false), status(HTTPRepository::REPO_NO_ERROR),
totalDownloaded(0) {
;
}
~HTTPRepoPrivate();
HTTPRepository *p; // link back to outer
HTTP::Client *http;
std::string baseUrl;
SGPath basePath;
bool isUpdating;
HTTPRepository::ResultCode status;
HTTPDirectory_ptr rootDir;
size_t totalDownloaded;
HTTPRepository::SyncPredicate syncPredicate;
HTTP::Request_ptr updateFile(HTTPDirectory *dir, const std::string &name,
size_t sz);
HTTP::Request_ptr updateDir(HTTPDirectory *dir, const std::string &hash,
size_t sz);
void failedToGetRootIndex(HTTPRepository::ResultCode st);
void failedToUpdateChild(const SGPath &relativePath,
HTTPRepository::ResultCode fileStatus);
void updatedChildSuccessfully(const SGPath &relativePath);
void checkForComplete();
typedef std::vector<RepoRequestPtr> RequestVector;
RequestVector queuedRequests, activeRequests;
void makeRequest(RepoRequestPtr req);
enum class RequestFinish { Done, Retry };
void finishedRequest(const RepoRequestPtr &req, RequestFinish retryRequest);
HTTPDirectory *getOrCreateDirectory(const std::string &path);
bool deleteDirectory(const std::string &relPath, const SGPath &absPath);
typedef std::vector<HTTPDirectory_ptr> DirectoryVector;
DirectoryVector directories;
void scheduleUpdateOfChildren(HTTPDirectory *dir);
SGPath installedCopyPath;
int countDirtyHashCaches() const;
void flushHashCaches();
enum ProcessResult { ProcessContinue, ProcessDone, ProcessFailed };
using RepoProcessTask = std::function<ProcessResult(HTTPRepoPrivate *repo)>;
void addTask(RepoProcessTask task);
std::deque<RepoProcessTask> pendingTasks;
};
} // namespace simgear
+9
View File
@@ -56,6 +56,15 @@ Request::~Request()
} }
void Request::prepareForRetry() {
setReadyState(UNSENT);
_willClose = false;
_connectionCloseHeader = false;
_responseStatus = 0;
_responseLength = 0;
_receivedBodyBytes = 0;
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
Request* Request::done(const Callback& cb) Request* Request::done(const Callback& cb)
{ {
+7 -3
View File
@@ -208,7 +208,9 @@ public:
*/ */
bool serverSupportsPipelining() const; bool serverSupportsPipelining() const;
protected: virtual void prepareForRetry();
protected:
Request(const std::string& url, const std::string method = "GET"); Request(const std::string& url, const std::string method = "GET");
virtual void requestStart(); virtual void requestStart();
@@ -222,12 +224,14 @@ protected:
virtual void onFail(); virtual void onFail();
virtual void onAlways(); virtual void onAlways();
void setFailure(int code, const std::string& reason);
void setSuccess(int code); void setSuccess(int code);
private: void setFailure(int code, const std::string &reason);
private:
friend class Client; friend class Client;
friend class Connection; friend class Connection;
friend class ContentDecoder; friend class ContentDecoder;
friend class TestApi;
Request(const Request&); // = delete; Request(const Request&); // = delete;
Request& operator=(const Request&); // = delete; Request& operator=(const Request&); // = delete;
+45
View File
@@ -0,0 +1,45 @@
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#pragma once
#include <functional>
#include "HTTPRequest.hxx"
namespace simgear {
namespace HTTP {
class Client;
using ResponseDoneCallback =
std::function<bool(int curlResult, Request_ptr req)>;
/**
* @brief this API is for unit-testing HTTP code.
* Don't use it for anything else. It's for unit-testing.
*/
class TestApi {
public:
// alow test suite to manipulate requests to simulate network errors;
// without this, it's hard to provoke certain failures in a loop-back
// network sitation.
static void setResponseDoneCallback(Client *cl, ResponseDoneCallback cb);
static void markRequestAsFailed(Request_ptr req, int curlCode,
const std::string &message);
};
} // namespace HTTP
} // namespace simgear
+4 -1
View File
@@ -185,6 +185,9 @@ gzfilebuf::setcompressionstrategy( int comp_strategy )
z_off_t z_off_t
gzfilebuf::approxOffset() { gzfilebuf::approxOffset() {
#ifdef __OpenBSD__
z_off_t res = 0;
#else
z_off_t res = gzoffset(file); z_off_t res = gzoffset(file);
if (res == -1) { if (res == -1) {
@@ -201,7 +204,7 @@ gzfilebuf::approxOffset() {
SG_LOG( SG_GENERAL, SG_ALERT, errMsg ); SG_LOG( SG_GENERAL, SG_ALERT, errMsg );
throw sg_io_exception(errMsg); throw sg_io_exception(errMsg);
} }
#endif
return res; return res;
} }
+7
View File
@@ -42,6 +42,7 @@
#include <simgear/bucket/newbucket.hxx> #include <simgear/bucket/newbucket.hxx>
#include <simgear/misc/sg_path.hxx> #include <simgear/misc/sg_path.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/math/SGGeometry.hxx> #include <simgear/math/SGGeometry.hxx>
#include <simgear/structure/exception.hxx> #include <simgear/structure/exception.hxx>
@@ -563,6 +564,12 @@ bool SGBinObject::read_bin( const SGPath& file ) {
// read headers // read headers
unsigned int header; unsigned int header;
sgReadUInt( fp, &header ); sgReadUInt( fp, &header );
if (sgReadError()) {
gzclose(fp);
throw sg_io_exception("Unable to read BTG header: " + simgear::strutils::error_string(errno), sg_location(file));
}
if ( ((header & 0xFF000000) >> 24) == 'S' && if ( ((header & 0xFF000000) >> 24) == 'S' &&
((header & 0x00FF0000) >> 16) == 'G' ) { ((header & 0x00FF0000) >> 16) == 'G' ) {
+16 -6
View File
@@ -71,21 +71,31 @@ SGFile::~SGFile() {
std::string SGFile::computeHash() std::string SGFile::computeHash()
{ {
if (!file_name.exists()) if (!file_name.exists())
return std::string(); return {};
simgear::sha1nfo info; simgear::sha1nfo info;
sha1_init(&info); sha1_init(&info);
char* buf = static_cast<char*>(malloc(1024 * 1024));
// unique_ptr with custom deleter for exception safety
const int bufSize = 1024 * 1024;
std::unique_ptr<char, std::function<void(char*)>> buf{static_cast<char*>(malloc(bufSize)),
[](char* p) { free(p); }};
if (!buf) {
SG_LOG(SG_IO, SG_ALERT, "Failed to malloc buffer for SHA1 check");
}
size_t readLen; size_t readLen;
SGBinaryFile f(file_name); SGBinaryFile f(file_name);
if (!f.open(SG_IO_IN)) { if (!f.open(SG_IO_IN)) {
throw sg_io_exception("Couldn't open file for compute hash", file_name); SG_LOG(SG_IO, SG_ALERT, "SGFile::computeHash: Failed to open " << file_name);
return {};
} }
while ((readLen = f.read(buf, 1024 * 1024)) > 0) { while ((readLen = f.read(buf.get(), bufSize)) > 0) {
sha1_write(&info, buf, readLen); sha1_write(&info, buf.get(), readLen);
} }
f.close(); f.close();
free(buf);
std::string hashBytes((char*)sha1_result(&info), HASH_LENGTH); std::string hashBytes((char*)sha1_result(&info), HASH_LENGTH);
return simgear::strutils::encodeHex(hashBytes); return simgear::strutils::encodeHex(hashBytes);
} }
+159 -16
View File
@@ -1,16 +1,18 @@
#include <cassert> #include <cassert>
#include <cstdlib> #include <cstdlib>
#include <errno.h>
#include <fcntl.h>
#include <functional>
#include <iostream> #include <iostream>
#include <map> #include <map>
#include <sstream> #include <sstream>
#include <errno.h>
#include <fcntl.h>
#include <simgear/simgear_config.h> #include <simgear/simgear_config.h>
#include "test_HTTP.hxx"
#include "HTTPRepository.hxx"
#include "HTTPClient.hxx" #include "HTTPClient.hxx"
#include "HTTPRepository.hxx"
#include "HTTPTestApi_private.hxx"
#include "test_HTTP.hxx"
#include <simgear/misc/strutils.hxx> #include <simgear/misc/strutils.hxx>
#include <simgear/misc/sg_hash.hxx> #include <simgear/misc/sg_hash.hxx>
@@ -25,6 +27,8 @@
using namespace simgear; using namespace simgear;
using TestApi = simgear::HTTP::TestApi;
std::string dataForFile(const std::string& parentName, const std::string& name, int revision) std::string dataForFile(const std::string& parentName, const std::string& name, int revision)
{ {
std::ostringstream os; std::ostringstream os;
@@ -45,6 +49,9 @@ std::string hashForData(const std::string& d)
return strutils::encodeHex(sha1_result(&info), HASH_LENGTH); return strutils::encodeHex(sha1_result(&info), HASH_LENGTH);
} }
class TestRepoEntry;
using AccessCallback = std::function<void(TestRepoEntry &entry)>;
class TestRepoEntry class TestRepoEntry
{ {
public: public:
@@ -70,7 +77,8 @@ public:
int requestCount; int requestCount;
bool getWillFail; bool getWillFail;
bool returnCorruptData; bool returnCorruptData;
std::unique_ptr<SGCallback> accessCallback;
AccessCallback accessCallback;
void clearRequestCounts(); void clearRequestCounts();
@@ -270,8 +278,8 @@ public:
return; return;
} }
if (entry->accessCallback.get()) { if (entry->accessCallback) {
(*entry->accessCallback)(); entry->accessCallback(*entry);
} }
if (entry->getWillFail) { if (entry->getWillFail) {
@@ -282,20 +290,29 @@ public:
entry->requestCount++; entry->requestCount++;
std::string content; std::string content;
bool closeSocket = false;
size_t contentSize = 0;
if (entry->returnCorruptData) { if (entry->returnCorruptData) {
content = dataForFile("!$£$!" + entry->parent->name, content = dataForFile("!$£$!" + entry->parent->name,
"corrupt_" + entry->name, "corrupt_" + entry->name,
entry->revision); entry->revision);
contentSize = content.size();
} else { } else {
content = entry->data(); content = entry->data();
contentSize = content.size();
} }
std::stringstream d; std::stringstream d;
d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n"; d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
d << "Content-Length:" << content.size() << "\r\n"; d << "Content-Length:" << contentSize << "\r\n";
d << "\r\n"; // final CRLF to terminate the headers d << "\r\n"; // final CRLF to terminate the headers
d << content; d << content;
push(d.str().c_str()); push(d.str().c_str());
if (closeSocket) {
closeWhenDone();
}
} else { } else {
sendErrorResponse(404, false, ""); sendErrorResponse(404, false, "");
} }
@@ -392,6 +409,7 @@ void waitForUpdateComplete(HTTP::Client* cl, HTTPRepository* repo)
cl->update(); cl->update();
testServer.poll(); testServer.poll();
repo->process();
if (!repo->isDoingSync()) { if (!repo->isDoingSync()) {
return; return;
} }
@@ -401,6 +419,16 @@ void waitForUpdateComplete(HTTP::Client* cl, HTTPRepository* repo)
std::cerr << "timed out" << std::endl; std::cerr << "timed out" << std::endl;
} }
void runForTime(HTTP::Client *cl, HTTPRepository *repo, int msec = 15) {
SGTimeStamp start(SGTimeStamp::now());
while (start.elapsedMSec() < msec) {
cl->update();
testServer.poll();
repo->process();
SGTimeStamp::sleepForMSec(1);
}
}
void testBasicClone(HTTP::Client* cl) void testBasicClone(HTTP::Client* cl)
{ {
std::unique_ptr<HTTPRepository> repo; std::unique_ptr<HTTPRepository> repo;
@@ -618,9 +646,19 @@ void testAbandonCorruptFiles(HTTP::Client* cl)
repo->setBaseUrl("http://localhost:2000/repo"); repo->setBaseUrl("http://localhost:2000/repo");
repo->update(); repo->update();
waitForUpdateComplete(cl, repo.get()); waitForUpdateComplete(cl, repo.get());
if (repo->failure() != HTTPRepository::REPO_ERROR_CHECKSUM) { if (repo->failure() != HTTPRepository::REPO_PARTIAL_UPDATE) {
std::cerr << "Got failure state:" << repo->failure() << std::endl; std::cerr << "Got failure state:" << repo->failure() << std::endl;
throw sg_exception("Bad result from corrupt files test"); throw sg_exception("Bad result from corrupt files test");
}
auto failedFiles = repo->failures();
if (failedFiles.size() != 1) {
throw sg_exception("Bad result from corrupt files test");
}
if (failedFiles.front().path.utf8Str() != "dirB/subdirG/fileBGA") {
throw sg_exception("Bad path from corrupt files test:" +
failedFiles.front().path.utf8Str());
} }
repo.reset(); repo.reset();
@@ -657,15 +695,21 @@ void testServerModifyDuringSync(HTTP::Client* cl)
repo.reset(new HTTPRepository(p, cl)); repo.reset(new HTTPRepository(p, cl));
repo->setBaseUrl("http://localhost:2000/repo"); repo->setBaseUrl("http://localhost:2000/repo");
global_repo->findEntry("dirA/fileAA")->accessCallback.reset(make_callback(&modifyBTree)); global_repo->findEntry("dirA/fileAA")->accessCallback =
[](const TestRepoEntry &r) {
std::cout << "Modifying sub-tree" << std::endl;
global_repo->findEntry("dirB/subdirA/fileBAC")->revision++;
global_repo->defineFile("dirB/subdirZ/fileBZA");
global_repo->findEntry("dirB/subdirB/fileBBB")->revision++;
};
repo->update(); repo->update();
waitForUpdateComplete(cl, repo.get()); waitForUpdateComplete(cl, repo.get());
global_repo->findEntry("dirA/fileAA")->accessCallback.reset(); global_repo->findEntry("dirA/fileAA")->accessCallback = AccessCallback{};
if (repo->failure() != HTTPRepository::REPO_ERROR_CHECKSUM) { if (repo->failure() != HTTPRepository::REPO_PARTIAL_UPDATE) {
throw sg_exception("Bad result from modify during sync test"); throw sg_exception("Bad result from modify during sync test");
} }
std::cout << "Passed test modify server during sync" << std::endl; std::cout << "Passed test modify server during sync" << std::endl;
@@ -755,6 +799,103 @@ void testCopyInstalledChildren(HTTP::Client* cl)
std::cout << "passed Copy installed children" << std::endl; std::cout << "passed Copy installed children" << std::endl;
} }
void testRetryAfterSocketFailure(HTTP::Client *cl) {
global_repo->clearRequestCounts();
global_repo->clearFailFlags();
std::unique_ptr<HTTPRepository> repo;
SGPath p(simgear::Dir::current().path());
p.append("http_repo_retry_after_socket_fail");
simgear::Dir pd(p);
if (pd.exists()) {
pd.removeChildren();
}
repo.reset(new HTTPRepository(p, cl));
repo->setBaseUrl("http://localhost:2000/repo");
int aaFailsRemaining = 2;
int subdirBAFailsRemaining = 2;
TestApi::setResponseDoneCallback(
cl, [&aaFailsRemaining, &subdirBAFailsRemaining](int curlResult,
HTTP::Request_ptr req) {
if (req->url() == "http://localhost:2000/repo/dirA/fileAA") {
if (aaFailsRemaining == 0)
return false;
--aaFailsRemaining;
TestApi::markRequestAsFailed(req, 56, "Simulated socket failure");
return true;
} else if (req->url() ==
"http://localhost:2000/repo/dirB/subdirA/.dirindex") {
if (subdirBAFailsRemaining == 0)
return false;
--subdirBAFailsRemaining;
TestApi::markRequestAsFailed(req, 56, "Simulated socket failure");
return true;
} else {
return false;
}
});
repo->update();
waitForUpdateComplete(cl, repo.get());
if (repo->failure() != HTTPRepository::REPO_NO_ERROR) {
throw sg_exception("Bad result from retry socket failure test");
}
verifyFileState(p, "dirA/fileAA");
verifyFileState(p, "dirB/subdirA/fileBAA");
verifyFileState(p, "dirB/subdirA/fileBAC");
verifyRequestCount("dirA/fileAA", 3);
verifyRequestCount("dirB/subdirA", 3);
verifyRequestCount("dirB/subdirA/fileBAC", 1);
}
void testPersistentSocketFailure(HTTP::Client *cl) {
global_repo->clearRequestCounts();
global_repo->clearFailFlags();
std::unique_ptr<HTTPRepository> repo;
SGPath p(simgear::Dir::current().path());
p.append("http_repo_persistent_socket_fail");
simgear::Dir pd(p);
if (pd.exists()) {
pd.removeChildren();
}
repo.reset(new HTTPRepository(p, cl));
repo->setBaseUrl("http://localhost:2000/repo");
TestApi::setResponseDoneCallback(
cl, [](int curlResult, HTTP::Request_ptr req) {
const auto url = req->url();
if (url.find("http://localhost:2000/repo/dirB") == 0) {
TestApi::markRequestAsFailed(req, 56, "Simulated socket failure");
return true;
}
return false;
});
repo->update();
waitForUpdateComplete(cl, repo.get());
if (repo->failure() != HTTPRepository::REPO_PARTIAL_UPDATE) {
throw sg_exception("Bad result from retry socket failure test");
}
verifyFileState(p, "dirA/fileAA");
verifyRequestCount("dirA/fileAA", 1);
verifyRequestCount("dirD/fileDA", 1);
verifyRequestCount("dirD/subdirDA/fileDAA", 1);
verifyRequestCount("dirD/subdirDB/fileDBA", 1);
}
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
sglog().setLogLevels( SG_ALL, SG_INFO ); sglog().setLogLevels( SG_ALL, SG_INFO );
@@ -800,6 +941,8 @@ int main(int argc, char* argv[])
cl.clearAllConnections(); cl.clearAllConnections();
testCopyInstalledChildren(&cl); testCopyInstalledChildren(&cl);
testRetryAfterSocketFailure(&cl);
testPersistentSocketFailure(&cl);
std::cout << "all tests passed ok" << std::endl; std::cout << "all tests passed ok" << std::endl;
return 0; return 0;
+3 -1
View File
@@ -31,6 +31,8 @@
#include <simgear/sg_inlines.h> #include <simgear/sg_inlines.h>
#include <simgear/io/sg_file.hxx> #include <simgear/io/sg_file.hxx>
#include <simgear/misc/sg_dir.hxx> #include <simgear/misc/sg_dir.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/io/iostreams/sgstream.hxx> #include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/debug/logstream.hxx> #include <simgear/debug/logstream.hxx>
#include <simgear/package/unzip.h> #include <simgear/package/unzip.h>
@@ -592,7 +594,7 @@ public:
outFile.open(path, std::ios::binary | std::ios::trunc | std::ios::out); outFile.open(path, std::ios::binary | std::ios::trunc | std::ios::out);
if (outFile.fail()) { if (outFile.fail()) {
throw sg_io_exception("failed to open output file for writing", path); throw sg_io_exception("failed to open output file for writing:" + strutils::error_string(errno), path);
} }
while (!eof) { while (!eof) {
+5
View File
@@ -49,6 +49,11 @@ ResourceManager* ResourceManager::instance()
return static_manager; return static_manager;
} }
bool ResourceManager::haveInstance()
{
return static_manager != nullptr;
}
ResourceManager::~ResourceManager() ResourceManager::~ResourceManager()
{ {
assert(this == static_manager); assert(this == static_manager);
+2
View File
@@ -47,6 +47,8 @@ public:
static ResourceManager* instance(); static ResourceManager* instance();
static bool haveInstance();
static void reset(); static void reset();
/** /**
+20 -3
View File
@@ -44,6 +44,7 @@
#if defined(SG_WINDOWS) #if defined(SG_WINDOWS)
# include <direct.h> # include <direct.h>
# include <sys/utime.h> # include <sys/utime.h>
# include <Shlwapi.h>
#endif #endif
#include "sg_path.hxx" #include "sg_path.hxx"
@@ -194,7 +195,8 @@ SGPath::SGPath(PermissionChecker validator)
_permission_checker(validator), _permission_checker(validator),
_cached(false), _cached(false),
_rwCached(false), _rwCached(false),
_cacheEnabled(true) _cacheEnabled(true),
_existsCached(false)
{ {
} }
@@ -205,7 +207,8 @@ SGPath::SGPath( const std::string& p, PermissionChecker validator )
_permission_checker(validator), _permission_checker(validator),
_cached(false), _cached(false),
_rwCached(false), _rwCached(false),
_cacheEnabled(true) _cacheEnabled(true),
_existsCached(false)
{ {
fix(); fix();
} }
@@ -230,7 +233,8 @@ SGPath::SGPath( const SGPath& p,
_permission_checker(validator), _permission_checker(validator),
_cached(false), _cached(false),
_rwCached(false), _rwCached(false),
_cacheEnabled(p._cacheEnabled) _cacheEnabled(p._cacheEnabled),
_existsCached(false)
{ {
append(r); append(r);
fix(); fix();
@@ -510,6 +514,19 @@ void SGPath::checkAccess() const
bool SGPath::exists() const bool SGPath::exists() const
{ {
#if defined(SG_WINDOWS)
// optimisation: _wstat is slow, eg for TerraSync
if (!_cached && !_existsCached) {
std::wstring w(wstr());
if ((path.length() > 1) && (path.back() == '/')) {
w.pop_back();
}
_existsCached = true;
_exists = PathFileExistsW(w.c_str());
return _exists;
}
#endif
validate(); validate();
return _exists; return _exists;
} }
+1
View File
@@ -356,6 +356,7 @@ private:
mutable bool _exists : 1; mutable bool _exists : 1;
mutable bool _isDir : 1; mutable bool _isDir : 1;
mutable bool _isFile : 1; mutable bool _isFile : 1;
mutable bool _existsCached : 1; ///< only used on Windows
mutable time_t _modTime; mutable time_t _modTime;
mutable size_t _size; mutable size_t _size;
}; };
+63 -6
View File
@@ -643,16 +643,53 @@ void Catalog::processAlternate(SGPropertyNode_ptr alt)
return; return;
} }
// we have an alternate ID, and it's differnt from our ID, so let's // we have an alternate ID, and it's different from our ID, so let's
// define a new catalog // define a new catalog
if (!altId.empty()) { if (!altId.empty()) {
SG_LOG(SG_GENERAL, SG_INFO, "Adding new catalog:" << altId << " as version alternate for " << id()); // don't auto-re-add Catalogs the user has explicilty rmeoved, that would
// new catalog being added // suck
createFromUrl(root(), altUrl); const auto removedByUser = root()->explicitlyRemovedCatalogs();
auto it = std::find(removedByUser.begin(), removedByUser.end(), altId);
// and we can go idle now if (it != removedByUser.end()) {
changeStatus(Delegate::FAIL_VERSION); changeStatus(Delegate::FAIL_VERSION);
return; return;
}
SG_LOG(SG_GENERAL, SG_WARN,
"Adding new catalog:" << altId << " as version alternate for "
<< id());
// new catalog being added
auto newCat = createFromUrl(root(), altUrl);
bool didRun = false;
newCat->m_migratedFrom = this;
auto migratePackagesCb = [didRun](Catalog *c) mutable {
// removing callbacks is awkward, so use this
// flag to only run once. (and hence, we need to be mutable)
if (didRun)
return;
if (c->status() == Delegate::STATUS_REFRESHED) {
didRun = true;
string_list existing;
for (const auto &pack : c->migratedFrom()->installedPackages()) {
existing.push_back(pack->id());
}
const int count = c->markPackagesForInstallation(existing);
SG_LOG(
SG_GENERAL, SG_INFO,
"Marked " << count
<< " packages from previous catalog for installation");
}
};
newCat->addStatusCallback(migratePackagesCb);
// and we can go idle now
changeStatus(Delegate::FAIL_VERSION);
return;
} }
SG_LOG(SG_GENERAL, SG_INFO, "Migrating catalog " << id() << " to new URL:" << altUrl); SG_LOG(SG_GENERAL, SG_INFO, "Migrating catalog " << id() << " to new URL:" << altUrl);
@@ -661,6 +698,26 @@ void Catalog::processAlternate(SGPropertyNode_ptr alt)
root()->makeHTTPRequest(dl); root()->makeHTTPRequest(dl);
} }
int Catalog::markPackagesForInstallation(const string_list &packageIds) {
int result = 0;
for (const auto &id : packageIds) {
auto ourPkg = getPackageById(id);
if (!ourPkg)
continue;
auto existing = ourPkg->existingInstall();
if (!existing) {
ourPkg->markForInstall();
++result;
}
} // of outer package ID candidates iteration
return result;
}
CatalogRef Catalog::migratedFrom() const { return m_migratedFrom; }
} // of namespace pkg } // of namespace pkg
} // of namespace simgear } // of namespace simgear
+30 -2
View File
@@ -23,6 +23,7 @@
#include <map> #include <map>
#include <simgear/misc/sg_path.hxx> #include <simgear/misc/sg_path.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/props/props.hxx> #include <simgear/props/props.hxx>
#include <simgear/structure/SGReferenced.hxx> #include <simgear/structure/SGReferenced.hxx>
@@ -93,7 +94,7 @@ public:
/** /**
* retrieve all the packages in the catalog which are installed * retrieve all the packages in the catalog which are installed
* and have a pendig update * and have a pending update
*/ */
PackageList packagesNeedingUpdate() const; PackageList packagesNeedingUpdate() const;
@@ -151,7 +152,32 @@ public:
bool isUserEnabled() const; bool isUserEnabled() const;
void setUserEnabled(bool b); void setUserEnabled(bool b);
private:
/**
* Given a list of package IDs, mark all which exist in this package,
* for installation. ANy packahe IDs not present in this catalog,
* will be ignored.
*
* @result The number for packages newly marked for installation.
*/
int markPackagesForInstallation(const string_list &packageIds);
/**
* When a catalog is added due to migration, this will contain the
* Catalog which triggered the add. Usually this will be a catalog
* corresponding to an earlier version.
*
* Note it's only valid at the time, the migration actually took place;
* when the new catalog is loaded from disk, this value will return
* null.
*
* This is intended to allow Uis to show a 'catalog was migrated'
* feedback, when they see a catalog refresh, which has a non-null
* value of this method.
*/
CatalogRef migratedFrom() const;
private:
Catalog(Root* aRoot); Catalog(Root* aRoot);
class Downloader; class Downloader;
@@ -197,6 +223,8 @@ private:
PackageWeakMap m_variantDict; PackageWeakMap m_variantDict;
function_list<Callback> m_statusCallbacks; function_list<Callback> m_statusCallbacks;
CatalogRef m_migratedFrom;
}; };
} // of namespace pkg } // of namespace pkg
+130 -1
View File
@@ -828,6 +828,9 @@ void testVersionMigrateToId(HTTP::Client* cl)
it = std::find(enabledCats.begin(), enabledCats.end(), altCat); it = std::find(enabledCats.begin(), enabledCats.end(), altCat);
SG_VERIFY(it != enabledCats.end()); SG_VERIFY(it != enabledCats.end());
SG_CHECK_EQUAL(altCat->packagesNeedingUpdate().size(),
1); // should be the 737
// install a parallel package from the new catalog // install a parallel package from the new catalog
pkg::PackageRef p2 = root->getPackageById("org.flightgear.test.catalog-alt.b737-NG"); pkg::PackageRef p2 = root->getPackageById("org.flightgear.test.catalog-alt.b737-NG");
SG_CHECK_EQUAL(p2->id(), "b737-NG"); SG_CHECK_EQUAL(p2->id(), "b737-NG");
@@ -841,7 +844,7 @@ void testVersionMigrateToId(HTTP::Client* cl)
SG_CHECK_EQUAL(p2, p3); SG_CHECK_EQUAL(p2, p3);
} }
// test that re-init-ing doesn't mirgate again // test that re-init-ing doesn't migrate again
{ {
pkg::RootRef root(new pkg::Root(rootPath, "7.5")); pkg::RootRef root(new pkg::Root(rootPath, "7.5"));
root->setHTTPClient(cl); root->setHTTPClient(cl);
@@ -1184,6 +1187,128 @@ void testMirrorsFailure(HTTP::Client* cl)
} }
void testMigrateInstalled(HTTP::Client *cl) {
SGPath rootPath(simgear::Dir::current().path());
rootPath.append("pkg_migrate_installed");
simgear::Dir pd(rootPath);
pd.removeChildren();
pkg::RootRef root(new pkg::Root(rootPath, "8.1.2"));
root->setHTTPClient(cl);
pkg::CatalogRef oldCatalog, newCatalog;
{
oldCatalog = pkg::Catalog::createFromUrl(
root.ptr(), "http://localhost:2000/catalogTest1/catalog.xml");
waitForUpdateComplete(cl, root);
pkg::PackageRef p1 =
root->getPackageById("org.flightgear.test.catalog1.b747-400");
p1->install();
auto p2 = root->getPackageById("org.flightgear.test.catalog1.c172p");
p2->install();
auto p3 = root->getPackageById("org.flightgear.test.catalog1.b737-NG");
p3->install();
waitForUpdateComplete(cl, root);
}
{
newCatalog = pkg::Catalog::createFromUrl(
root.ptr(), "http://localhost:2000/catalogTest2/catalog.xml");
waitForUpdateComplete(cl, root);
string_list existing;
for (const auto &pack : oldCatalog->installedPackages()) {
existing.push_back(pack->id());
}
SG_CHECK_EQUAL(4, existing.size());
int result = newCatalog->markPackagesForInstallation(existing);
SG_CHECK_EQUAL(2, result);
SG_CHECK_EQUAL(2, newCatalog->packagesNeedingUpdate().size());
auto p1 = root->getPackageById("org.flightgear.test.catalog2.b737-NG");
auto ins = p1->existingInstall();
SG_CHECK_EQUAL(0, ins->revsion());
}
{
root->scheduleAllUpdates();
waitForUpdateComplete(cl, root);
SG_CHECK_EQUAL(0, newCatalog->packagesNeedingUpdate().size());
auto p1 = root->getPackageById("org.flightgear.test.catalog2.b737-NG");
auto ins = p1->existingInstall();
SG_CHECK_EQUAL(ins->revsion(), p1->revision());
}
}
void testDontMigrateRemoved(HTTP::Client *cl) {
global_catalogVersion = 2; // version which has migration info
SGPath rootPath(simgear::Dir::current().path());
rootPath.append("cat_dont_migrate_id");
simgear::Dir pd(rootPath);
pd.removeChildren();
// install and mnaully remove the alt catalog
{
pkg::RootRef root(new pkg::Root(rootPath, "8.1.2"));
root->setHTTPClient(cl);
pkg::CatalogRef c = pkg::Catalog::createFromUrl(
root.ptr(), "http://localhost:2000/catalogTest1/catalog-alt.xml");
waitForUpdateComplete(cl, root);
root->removeCatalogById("org.flightgear.test.catalog-alt");
}
// install the migration catalog
{
pkg::RootRef root(new pkg::Root(rootPath, "8.1.2"));
root->setHTTPClient(cl);
pkg::CatalogRef c = pkg::Catalog::createFromUrl(
root.ptr(), "http://localhost:2000/catalogTest1/catalog.xml");
waitForUpdateComplete(cl, root);
SG_VERIFY(c->isEnabled());
}
// change version to an alternate one
{
pkg::RootRef root(new pkg::Root(rootPath, "7.5"));
auto removed = root->explicitlyRemovedCatalogs();
auto j = std::find(removed.begin(), removed.end(),
"org.flightgear.test.catalog-alt");
SG_VERIFY(j != removed.end());
root->setHTTPClient(cl);
// this would tirgger migration, but we blocked it
root->refresh(true);
waitForUpdateComplete(cl, root);
pkg::CatalogRef cat = root->getCatalogById("org.flightgear.test.catalog1");
SG_VERIFY(!cat->isEnabled());
SG_CHECK_EQUAL(cat->status(), pkg::Delegate::FAIL_VERSION);
SG_CHECK_EQUAL(cat->id(), "org.flightgear.test.catalog1");
SG_CHECK_EQUAL(cat->url(),
"http://localhost:2000/catalogTest1/catalog.xml");
auto enabledCats = root->catalogs();
auto it = std::find(enabledCats.begin(), enabledCats.end(), cat);
SG_VERIFY(it == enabledCats.end());
// check the new catalog
auto altCat = root->getCatalogById("org.flightgear.test.catalog-alt");
SG_VERIFY(altCat.get() == nullptr);
}
}
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
sglog().setLogLevels( SG_ALL, SG_WARN ); sglog().setLogLevels( SG_ALL, SG_WARN );
@@ -1229,6 +1354,10 @@ int main(int argc, char* argv[])
testMirrorsFailure(&cl); testMirrorsFailure(&cl);
testMigrateInstalled(&cl);
testDontMigrateRemoved(&cl);
cerr << "Successfully passed all tests!" << endl; cerr << "Successfully passed all tests!" << endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
+32 -1
View File
@@ -198,6 +198,11 @@ InstallRef Package::install()
{ {
InstallRef ins = existingInstall(); InstallRef ins = existingInstall();
if (ins) { if (ins) {
// if there's updates, treat this as a 'start update' request
if (ins->hasUpdate()) {
m_catalog->root()->scheduleToUpdate(ins);
}
return ins; return ins;
} }
@@ -210,13 +215,39 @@ InstallRef Package::install()
return ins; return ins;
} }
InstallRef Package::markForInstall() {
InstallRef ins = existingInstall();
if (ins) {
return ins;
}
const auto pd = pathOnDisk();
Dir dir(pd);
if (!dir.create(0700)) {
SG_LOG(SG_IO, SG_ALERT,
"Package::markForInstall: couldn't create directory at:" << pd);
return {};
}
ins = new Install{this, pd};
_install_cb(this, ins); // not sure if we should trigger the callback for this
// repeat for dependencies to be kind
for (auto dep : dependencies()) {
dep->markForInstall();
}
return ins;
}
InstallRef Package::existingInstall(const InstallCallback& cb) const InstallRef Package::existingInstall(const InstallCallback& cb) const
{ {
InstallRef install; InstallRef install;
try { try {
install = m_catalog->root()->existingInstallForPackage(const_cast<Package*>(this)); install = m_catalog->root()->existingInstallForPackage(const_cast<Package*>(this));
} catch (std::exception& ) { } catch (std::exception& ) {
return InstallRef(); return {};
} }
if( cb ) if( cb )
+7
View File
@@ -61,6 +61,13 @@ public:
InstallRef InstallRef
existingInstall(const InstallCallback& cb = InstallCallback()) const; existingInstall(const InstallCallback& cb = InstallCallback()) const;
/**
* Mark this package for installation, but don't actually start the
* download process. This creates the on-disk placeholder, so
* the package will appear an eededing to be updated.
*/
InstallRef markForInstall();
bool isInstalled() const; bool isInstalled() const;
/** /**
+56 -6
View File
@@ -283,6 +283,32 @@ public:
fireDataForThumbnail(url, reinterpret_cast<const uint8_t*>(bytes.data()), bytes.size()); fireDataForThumbnail(url, reinterpret_cast<const uint8_t*>(bytes.data()), bytes.size());
} }
void writeRemovedCatalogsFile() const {
SGPath p = path / "RemovedCatalogs";
sg_ofstream stream(p, std::ios::out | std::ios::trunc | std::ios::binary);
for (const auto &cid : manuallyRemovedCatalogs) {
stream << cid << "\n";
}
stream.close();
}
void loadRemovedCatalogsFile() {
manuallyRemovedCatalogs.clear();
SGPath p = path / "RemovedCatalogs";
if (!p.exists())
return;
sg_ifstream stream(p, std::ios::in);
while (!stream.eof()) {
std::string line;
std::getline(stream, line);
const auto trimmed = strutils::strip(line);
if (!trimmed.empty()) {
manuallyRemovedCatalogs.push_back(trimmed);
}
} // of lines iteration
}
DelegateVec delegates; DelegateVec delegates;
SGPath path; SGPath path;
@@ -312,6 +338,9 @@ public:
typedef std::map<PackageRef, InstallRef> InstallCache; typedef std::map<PackageRef, InstallRef> InstallCache;
InstallCache m_installs; InstallCache m_installs;
/// persistent list of catalogs the user has manually removed
string_list manuallyRemovedCatalogs;
}; };
@@ -400,6 +429,8 @@ Root::Root(const SGPath& aPath, const std::string& aVersion) :
thumbsCacheDir.create(0755); thumbsCacheDir.create(0755);
} }
d->loadRemovedCatalogsFile();
for (SGPath c : dir.children(Dir::TYPE_DIR | Dir::NO_DOT_OR_DOTDOT)) { for (SGPath c : dir.children(Dir::TYPE_DIR | Dir::NO_DOT_OR_DOTDOT)) {
// note this will set the catalog status, which will insert into // note this will set the catalog status, which will insert into
// disabled catalogs automatically if necesary // disabled catalogs automatically if necesary
@@ -621,6 +652,13 @@ void Root::scheduleToUpdate(InstallRef aInstall)
} }
} }
void Root::scheduleAllUpdates() {
auto toBeUpdated = packagesNeedingUpdate(); // make a copy
for (const auto &u : toBeUpdated) {
scheduleToUpdate(u->existingInstall());
}
}
bool Root::isInstallQueued(InstallRef aInstall) const bool Root::isInstallQueued(InstallRef aInstall) const
{ {
auto it = std::find(d->updateDeque.begin(), d->updateDeque.end(), aInstall); auto it = std::find(d->updateDeque.begin(), d->updateDeque.end(), aInstall);
@@ -684,12 +722,6 @@ void Root::catalogRefreshStatus(CatalogRef aCat, Delegate::StatusCode aReason)
auto catIt = d->catalogs.find(aCat->id()); auto catIt = d->catalogs.find(aCat->id());
d->fireRefreshStatus(aCat, aReason); d->fireRefreshStatus(aCat, aReason);
if (aReason == Delegate::STATUS_IN_PROGRESS) {
d->refreshing.insert(aCat);
} else {
d->refreshing.erase(aCat);
}
if (aCat->isUserEnabled() && if (aCat->isUserEnabled() &&
(aReason == Delegate::STATUS_REFRESHED) && (aReason == Delegate::STATUS_REFRESHED) &&
(catIt == d->catalogs.end())) (catIt == d->catalogs.end()))
@@ -723,6 +755,17 @@ void Root::catalogRefreshStatus(CatalogRef aCat, Delegate::StatusCode aReason)
} }
} // of catalog is disabled } // of catalog is disabled
// remove from refreshing /after/ checking for enable / disabled, since for
// new catalogs, the reference in d->refreshing might be our /only/
// reference to the catalog. Once the refresh is done (either failed or
// succeeded) the Catalog will be in either d->catalogs or
// d->disabledCatalogs
if (aReason == Delegate::STATUS_IN_PROGRESS) {
d->refreshing.insert(aCat);
} else {
d->refreshing.erase(aCat);
}
if (d->refreshing.empty()) { if (d->refreshing.empty()) {
d->fireRefreshStatus(CatalogRef(), Delegate::STATUS_REFRESHED); d->fireRefreshStatus(CatalogRef(), Delegate::STATUS_REFRESHED);
d->firePackagesChanged(); d->firePackagesChanged();
@@ -783,6 +826,9 @@ bool Root::removeCatalogById(const std::string& aId)
<< "failed to remove directory"); << "failed to remove directory");
} }
d->manuallyRemovedCatalogs.push_back(aId);
d->writeRemovedCatalogsFile();
// notify that a catalog is being removed // notify that a catalog is being removed
d->firePackagesChanged(); d->firePackagesChanged();
@@ -854,6 +900,10 @@ void Root::unregisterInstall(InstallRef ins)
d->fireFinishUninstall(ins->package()); d->fireFinishUninstall(ins->package());
} }
string_list Root::explicitlyRemovedCatalogs() const {
return d->manuallyRemovedCatalogs;
}
} // of namespace pkg } // of namespace pkg
} // of namespace simgear } // of namespace simgear
+16 -1
View File
@@ -155,7 +155,22 @@ public:
void requestThumbnailData(const std::string& aUrl); void requestThumbnailData(const std::string& aUrl);
bool isInstallQueued(InstallRef aInstall) const; bool isInstallQueued(InstallRef aInstall) const;
private:
/**
* Mark all 'to be updated' packages for update now
*/
void scheduleAllUpdates();
/**
* @brief list of catalog IDs, the user has explicitly removed via
* removeCatalogById(). This is important to allow the user to opt-out
* of migrated packages.
*
* This information is stored in a helper file, in the root directory
*/
string_list explicitlyRemovedCatalogs() const;
private:
friend class Install; friend class Install;
friend class Catalog; friend class Catalog;
friend class Package; friend class Package;
+86
View File
@@ -0,0 +1,86 @@
<?xml version="1.0"?>
<PropertyList>
<id>org.flightgear.test.catalog2</id>
<description>Second test catalog</description>
<url>http://localhost:2000/catalogTest2/catalog.xml</url>
<catalog-version>4</catalog-version>
<version>8.1.*</version>
<version>8.0.0</version>
<version>8.2.0</version>
<package>
<id>alpha</id>
<name>Alpha package</name>
<revision type="int">8</revision>
<file-size-bytes type="int">593</file-size-bytes>
<md5>a469c4b837f0521db48616cfe65ac1ea</md5>
<url>http://localhost:2000/catalogTest1/alpha.zip</url>
<dir>alpha</dir>
</package>
<package>
<id>b737-NG</id>
<name>Boeing 737 NG</name>
<dir>b737NG</dir>
<description>A popular twin-engined narrow body jet</description>
<revision type="int">111</revision>
<file-size-bytes type="int">860</file-size-bytes>
<tag>boeing</tag>
<tag>jet</tag>
<tag>ifr</tag>
<!-- not within a localized element -->
<de>
<description>German description of B737NG XYZ</description>
</de>
<fr>
<description>French description of B737NG</description>
</fr>
<rating>
<FDM type="int">5</FDM>
<systems type="int">5</systems>
<model type="int">4</model>
<cockpit type="int">4</cockpit>
</rating>
<md5>a94ca5704f305b90767f40617d194ed6</md5>
<url>http://localhost:2000/mirrorA/b737.tar.gz</url>
<url>http://localhost:2000/mirrorB/b737.tar.gz</url>
<url>http://localhost:2000/mirrorC/b737.tar.gz</url>
</package>
<package>
<id>b747-400</id>
<name>Boeing 747-400</name>
<dir>b744</dir>
<description>A popular four-engined wide-body jet</description>
<revision type="int">111</revision>
<file-size-bytes type="int">860</file-size-bytes>
<tag>boeing</tag>
<tag>jet</tag>
<tag>ifr</tag>
<rating>
<FDM type="int">5</FDM>
<systems type="int">5</systems>
<model type="int">4</model>
<cockpit type="int">4</cockpit>
</rating>
<md5>4d3f7417d74f811aa20ccc4f35673d20</md5>
<!-- this URL will sometimes fail, on purpose -->
<url>http://localhost:2000/catalogTest1/b747.tar.gz</url>
</package>
</PropertyList>
+6
View File
@@ -59,6 +59,12 @@ void AtomicChangeListener::fireChangeListeners()
listeners.clear(); listeners.clear();
} }
void AtomicChangeListener::clearPendingChanges()
{
auto& listeners = ListenerListSingleton::instance()->listeners;
listeners.clear();
}
void AtomicChangeListener::valueChangedImplementation() void AtomicChangeListener::valueChangedImplementation()
{ {
if (!_dirty) { if (!_dirty) {
+10
View File
@@ -52,7 +52,17 @@ public:
bool isDirty() { return _dirty; } bool isDirty() { return _dirty; }
bool isValid() { return _valid; } bool isValid() { return _valid; }
virtual void unregister_property(SGPropertyNode* node) override; virtual void unregister_property(SGPropertyNode* node) override;
static void fireChangeListeners(); static void fireChangeListeners();
/**
* @brief Ensure we've deleted any pending changes.
*
* This is important in shutdown and reset, to avoid holding
* property listeners around after the property tree is destroyed
*/
static void clearPendingChanges();
private: private:
virtual void valueChangedImplementation() override; virtual void valueChangedImplementation() override;
virtual void valuesChanged(); virtual void valuesChanged();
+19
View File
@@ -19,6 +19,10 @@
#include <sstream> #include <sstream>
#include <iomanip> #include <iomanip>
#include <iterator> #include <iterator>
#include <exception> // can't use sg_exception becuase of PROPS_STANDALONE
#include <mutex>
#include <thread>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
@@ -51,6 +55,10 @@ using namespace simgear;
struct SGPropertyNodeListeners struct SGPropertyNodeListeners
{ {
/* Protect _num_iterators and _items. We use a recursive mutex to allow
nested access to work as normal. */
std::recursive_mutex _rmutex;
/* This keeps a count of the current number of nested invocations of /* This keeps a count of the current number of nested invocations of
forEachListener(). If non-zero, other code higher up the stack is iterating forEachListener(). If non-zero, other code higher up the stack is iterating
_items[] so for example code must not erase items in the vector. */ _items[] so for example code must not erase items in the vector. */
@@ -2406,6 +2414,7 @@ SGPropertyNode::addChangeListener (SGPropertyChangeListener * listener,
if (_listeners == 0) if (_listeners == 0)
_listeners = new SGPropertyNodeListeners; _listeners = new SGPropertyNodeListeners;
std::lock_guard<std::recursive_mutex> lock(_listeners->_rmutex);
/* If there's a nullptr entry (a listener that was unregistered), we /* If there's a nullptr entry (a listener that was unregistered), we
overwrite it. This ensures that listeners that routinely unregister+register overwrite it. This ensures that listeners that routinely unregister+register
themselves don't make _listeners->_items grow unnecessarily. Otherwise simply themselves don't make _listeners->_items grow unnecessarily. Otherwise simply
@@ -2429,9 +2438,13 @@ SGPropertyNode::removeChangeListener (SGPropertyChangeListener * listener)
{ {
if (_listeners == 0) if (_listeners == 0)
return; return;
/* We use a std::unique_lock rather than a std::lock_guard because we may
need to unlock early. */
std::unique_lock<std::recursive_mutex> lock(_listeners->_rmutex);
vector<SGPropertyChangeListener*>::iterator it = vector<SGPropertyChangeListener*>::iterator it =
find(_listeners->_items.begin(), _listeners->_items.end(), listener); find(_listeners->_items.begin(), _listeners->_items.end(), listener);
if (it != _listeners->_items.end()) { if (it != _listeners->_items.end()) {
assert(_listeners->_num_iterators >= 0);
if (_listeners->_num_iterators) { if (_listeners->_num_iterators) {
/* _listeners._items is currently being iterated further up the stack in /* _listeners._items is currently being iterated further up the stack in
this thread by one or more nested invocations of forEachListener(), so this thread by one or more nested invocations of forEachListener(), so
@@ -2450,6 +2463,7 @@ SGPropertyNode::removeChangeListener (SGPropertyChangeListener * listener)
_listeners->_items.erase(it); _listeners->_items.erase(it);
listener->unregister_property(this); listener->unregister_property(this);
if (_listeners->_items.empty()) { if (_listeners->_items.empty()) {
lock.unlock();
delete _listeners; delete _listeners;
_listeners = 0; _listeners = 0;
} }
@@ -2512,6 +2526,8 @@ static void forEachListener(
{ {
if (!_listeners) return; if (!_listeners) return;
std::lock_guard<std::recursive_mutex> lock(_listeners->_rmutex);
assert(_listeners->_num_iterators >= 0);
_listeners->_num_iterators += 1; _listeners->_num_iterators += 1;
/* We need to use an index here when iterating _listeners->_items, not an /* We need to use an index here when iterating _listeners->_items, not an
@@ -2530,8 +2546,10 @@ static void forEachListener(
} }
_listeners->_num_iterators -= 1; _listeners->_num_iterators -= 1;
assert(_listeners->_num_iterators >= 0);
if (_listeners->_num_iterators == 0) { if (_listeners->_num_iterators == 0) {
/* Remove any items that have been set to nullptr. */ /* Remove any items that have been set to nullptr. */
_listeners->_items.erase( _listeners->_items.erase(
std::remove(_listeners->_items.begin(), _listeners->_items.end(), (SGPropertyChangeListener*) nullptr), std::remove(_listeners->_items.begin(), _listeners->_items.end(), (SGPropertyChangeListener*) nullptr),
@@ -2547,6 +2565,7 @@ static void forEachListener(
int SGPropertyNode::nListeners() const int SGPropertyNode::nListeners() const
{ {
if (!_listeners) return 0; if (!_listeners) return 0;
std::lock_guard<std::recursive_mutex> lock(_listeners->_rmutex);
int n = 0; int n = 0;
for (auto listener: _listeners->_items) { for (auto listener: _listeners->_items) {
if (listener) n += 1; if (listener) n += 1;
+12 -6
View File
@@ -212,7 +212,8 @@ struct ReaderWriterSTG::_ModelBin {
STGObjectsQuadtree quadtree((GetModelLODCoord()), (AddModelLOD())); STGObjectsQuadtree quadtree((GetModelLODCoord()), (AddModelLOD()));
quadtree.buildQuadTree(_objectStaticList.begin(), _objectStaticList.end()); quadtree.buildQuadTree(_objectStaticList.begin(), _objectStaticList.end());
osg::ref_ptr<osg::Group> group = quadtree.getRoot(); osg::ref_ptr<osg::Group> group = quadtree.getRoot();
group->setName("STG-group-A"); string group_name = string("STG-group-A ").append(_bucket.gen_index_str());
group->setName(group_name);
group->setDataVariance(osg::Object::STATIC); group->setDataVariance(osg::Object::STATIC);
simgear::AirportSignBuilder signBuilder(_options->getMaterialLib(), _bucket.get_center()); simgear::AirportSignBuilder signBuilder(_options->getMaterialLib(), _bucket.get_center());
@@ -586,10 +587,12 @@ struct ReaderWriterSTG::_ModelBin {
{ {
osg::ref_ptr<SGReaderWriterOptions> options; osg::ref_ptr<SGReaderWriterOptions> options;
options = SGReaderWriterOptions::copyOrCreate(opt); options = SGReaderWriterOptions::copyOrCreate(opt);
float pagedLODExpiry = atoi(options->getPluginStringData("SimGear::PAGED_LOD_EXPIRY").c_str());
osg::ref_ptr<osg::Group> terrainGroup = new osg::Group; osg::ref_ptr<osg::Group> terrainGroup = new osg::Group;
terrainGroup->setDataVariance(osg::Object::STATIC); terrainGroup->setDataVariance(osg::Object::STATIC);
terrainGroup->setName("terrain"); std::string terrain_name = string("terrain ").append(bucket.gen_index_str());
terrainGroup->setName(terrain_name);
if (_foundBase) { if (_foundBase) {
for (auto stgObject : _objectList) { for (auto stgObject : _objectList) {
@@ -637,11 +640,13 @@ struct ReaderWriterSTG::_ModelBin {
} else { } else {
osg::PagedLOD* pagedLOD = new osg::PagedLOD; osg::PagedLOD* pagedLOD = new osg::PagedLOD;
pagedLOD->setCenterMode(osg::PagedLOD::USE_BOUNDING_SPHERE_CENTER); pagedLOD->setCenterMode(osg::PagedLOD::USE_BOUNDING_SPHERE_CENTER);
pagedLOD->setName("pagedObjectLOD"); std::string name = string("pagedObjectLOD ").append(bucket.gen_index_str());
pagedLOD->setName(name);
// This should be visible in any case. // This should be visible in any case.
// If this is replaced by some lower level of detail, the parent LOD node handles this. // If this is replaced by some lower level of detail, the parent LOD node handles this.
pagedLOD->addChild(terrainGroup, 0, std::numeric_limits<float>::max()); pagedLOD->addChild(terrainGroup, 0, std::numeric_limits<float>::max());
pagedLOD->setMinimumExpiryTime(0, pagedLODExpiry);
// we just need to know about the read file callback that itself holds the data // we just need to know about the read file callback that itself holds the data
osg::ref_ptr<DelayLoadReadFileCallback> readFileCallback = new DelayLoadReadFileCallback; osg::ref_ptr<DelayLoadReadFileCallback> readFileCallback = new DelayLoadReadFileCallback;
@@ -658,10 +663,11 @@ struct ReaderWriterSTG::_ModelBin {
// Objects may end up displayed up to 2x the object range. // Objects may end up displayed up to 2x the object range.
pagedLOD->setRange(pagedLOD->getNumChildren(), 0, 2.0 * _object_range_rough); pagedLOD->setRange(pagedLOD->getNumChildren(), 0, 2.0 * _object_range_rough);
pagedLOD->setMinimumExpiryTime(pagedLOD->getNumChildren(), pagedLODExpiry);
pagedLOD->setRadius(SG_TILE_RADIUS); pagedLOD->setRadius(SG_TILE_RADIUS);
SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile PagedLOD Center: " << pagedLOD->getCenter().x() << "," << pagedLOD->getCenter().y() << "," << pagedLOD->getCenter().z() ); SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile " << bucket.gen_index_str() << " PagedLOD Center: " << pagedLOD->getCenter().x() << "," << pagedLOD->getCenter().y() << "," << pagedLOD->getCenter().z() );
SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile PagedLOD Range: " << (2.0 * _object_range_rough)); SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile " << bucket.gen_index_str() << " PagedLOD Range: " << (2.0 * _object_range_rough));
SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile PagedLOD Radius: " << SG_TILE_RADIUS); SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile " << bucket.gen_index_str() << " PagedLOD Radius: " << SG_TILE_RADIUS);
return pagedLOD; return pagedLOD;
} }
} }
+107 -31
View File
@@ -334,6 +334,10 @@ public:
void runInternal(); void runInternal();
void updateSyncSlot(SyncSlot& slot); void updateSyncSlot(SyncSlot& slot);
void beginSyncAirports(SyncSlot& slot);
void beginSyncTile(SyncSlot& slot);
void beginNormalSync(SyncSlot& slot);
void drainWaitingTiles(); void drainWaitingTiles();
// commond helpers between both internal and external models // commond helpers between both internal and external models
@@ -547,6 +551,7 @@ void SGTerraSync::WorkerThread::run()
void SGTerraSync::WorkerThread::updateSyncSlot(SyncSlot &slot) void SGTerraSync::WorkerThread::updateSyncSlot(SyncSlot &slot)
{ {
if (slot.repository.get()) { if (slot.repository.get()) {
slot.repository->process();
if (slot.repository->isDoingSync()) { if (slot.repository->isDoingSync()) {
#if 1 #if 1
if (slot.stamp.elapsedMSec() > (int)slot.nextWarnTimeout) { if (slot.stamp.elapsedMSec() > (int)slot.nextWarnTimeout) {
@@ -588,23 +593,14 @@ void SGTerraSync::WorkerThread::updateSyncSlot(SyncSlot &slot)
SGPath path(_local_dir); SGPath path(_local_dir);
path.append(slot.currentItem._dir); path.append(slot.currentItem._dir);
slot.isNewDirectory = !path.exists(); slot.isNewDirectory = !path.exists();
if (slot.isNewDirectory) { const auto type = slot.currentItem._type;
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
slot.repository.reset(new HTTPRepository(path, &_http)); if (type == SyncItem::AirportData) {
slot.repository->setBaseUrl(_httpServer + "/" + slot.currentItem._dir); beginSyncAirports(slot);
} else if (type == SyncItem::Tile) {
if (_installRoot.exists()) { beginSyncTile(slot);
SGPath p = _installRoot; } else {
p.append(slot.currentItem._dir); beginNormalSync(slot);
slot.repository->setInstalledCopyPath(p);
} }
try { try {
@@ -627,6 +623,96 @@ void SGTerraSync::WorkerThread::updateSyncSlot(SyncSlot &slot)
} }
} }
void SGTerraSync::WorkerThread::beginSyncAirports(SyncSlot& slot)
{
if (!slot.isNewDirectory) {
beginNormalSync(slot);
return;
}
SG_LOG(SG_TERRASYNC, SG_INFO, "doing Airports download via tarball");
// we want to sync the 'root' TerraSync dir, but not all of it, just
// the Airports_archive.tar.gz file so we use our TerraSync local root
// as the path (since the archive will add Airports/)
slot.repository.reset(new HTTPRepository(_local_dir, &_http));
slot.repository->setBaseUrl(_httpServer);
// filter callback to *only* sync the Airport_archive tarball,
// and ensure no other contents are touched
auto f = [](const HTTPRepository::SyncItem& item) {
if (!item.directory.empty())
return false;
return (item.filename.find("Airports_archive.") == 0);
};
slot.repository->setFilter(f);
}
void SGTerraSync::WorkerThread::beginSyncTile(SyncSlot& slot)
{
// avoid 404 requests by doing a sync which excludes all paths
// except our tile path. In the case of a missing 1x1 tile, we will
// stop becuase all directories are filtered out, which is what we want
auto comps = strutils::split(slot.currentItem._dir, "/");
if (comps.size() != 3) {
SG_LOG(SG_TERRASYNC, SG_ALERT, "Bad tile path:" << slot.currentItem._dir);
beginNormalSync(slot);
return;
}
const auto tileCategory = comps.front();
const auto tenByTenDir = comps.at(1);
const auto oneByOneDir = comps.at(2);
const auto path = SGPath::fromUtf8(_local_dir) / tileCategory;
slot.repository.reset(new HTTPRepository(path, &_http));
slot.repository->setBaseUrl(_httpServer + "/" + tileCategory);
if (_installRoot.exists()) {
SGPath p = _installRoot / tileCategory;
slot.repository->setInstalledCopyPath(p);
}
const auto dirPrefix = tenByTenDir + "/" + oneByOneDir;
// filter callback to *only* sync the 1x1 dir we want, if it exists
// if doesn't, we'll simply stop, which is what we want
auto f = [tenByTenDir, oneByOneDir, dirPrefix](const HTTPRepository::SyncItem& item) {
// only allow the specific 10x10 and 1x1 dirs we want
if (item.directory.empty()) {
return item.filename == tenByTenDir;
} else if (item.directory == tenByTenDir) {
return item.filename == oneByOneDir;
}
// allow arbitrary children below dirPrefix, including sub-dirs
if (item.directory.find(dirPrefix) == 0) {
return true;
}
SG_LOG(SG_TERRASYNC, SG_ALERT, "Tile sync: saw weird path:" << item.directory << " file " << item.filename);
return false;
};
slot.repository->setFilter(f);
}
void SGTerraSync::WorkerThread::beginNormalSync(SyncSlot& slot)
{
SGPath path(_local_dir);
path.append(slot.currentItem._dir);
slot.repository.reset(new HTTPRepository(path, &_http));
slot.repository->setBaseUrl(_httpServer + "/" + slot.currentItem._dir);
if (_installRoot.exists()) {
SGPath p = _installRoot;
p.append(slot.currentItem._dir);
slot.repository->setInstalledCopyPath(p);
}
}
void SGTerraSync::WorkerThread::runInternal() void SGTerraSync::WorkerThread::runInternal()
{ {
while (!_stop) { while (!_stop) {
@@ -720,6 +806,7 @@ void SGTerraSync::WorkerThread::fail(SyncItem failedItem)
_state._fail_count++; _state._fail_count++;
failedItem._status = SyncItem::Failed; failedItem._status = SyncItem::Failed;
_freshTiles.push_back(failedItem); _freshTiles.push_back(failedItem);
// not we also end up here for partial syncs
SG_LOG(SG_TERRASYNC,SG_INFO, SG_LOG(SG_TERRASYNC,SG_INFO,
"Failed to sync'" << failedItem._dir << "'"); "Failed to sync'" << failedItem._dir << "'");
_completedTiles[ failedItem._dir ] = now + UpdateInterval::FailedAttempt; _completedTiles[ failedItem._dir ] = now + UpdateInterval::FailedAttempt;
@@ -1090,21 +1177,10 @@ bool SGTerraSync::isIdle() {return _workerThread->isIdle();}
void SGTerraSync::syncAirportsModels() void SGTerraSync::syncAirportsModels()
{ {
static const char* bounds = "MZAJKL"; // airport sync order: K-L, A-J, M-Z SyncItem w("Airports", SyncItem::AirportData);
// note "request" method uses LIFO order, i.e. processes most recent request first SyncItem a("Models", SyncItem::SharedModels);
for( unsigned i = 0; i < strlen(bounds)/2; i++ ) _workerThread->request(w);
{ _workerThread->request(a);
for ( char synced_other = bounds[2*i]; synced_other <= bounds[2*i+1]; synced_other++ )
{
ostringstream dir;
dir << "Airports/" << synced_other;
SyncItem w(dir.str(), SyncItem::AirportData);
_workerThread->request( w );
}
}
SyncItem w("Models", SyncItem::SharedModels);
_workerThread->request( w );
} }
string_list SGTerraSync::getSceneryPathSuffixes() const string_list SGTerraSync::getSceneryPathSuffixes() const
+6 -3
View File
@@ -380,7 +380,8 @@ namespace simgear
ALvoid* loadWAVFromFile(const SGPath& path, unsigned int& format, ALsizei& size, ALfloat& freqf, unsigned int& block_align) ALvoid* loadWAVFromFile(const SGPath& path, unsigned int& format, ALsizei& size, ALfloat& freqf, unsigned int& block_align)
{ {
if (!path.exists()) { if (!path.exists()) {
throw sg_io_exception("loadWAVFromFile: file not found", path); SG_LOG(SG_IO, SG_DEV_ALERT, "loadWAVFromFile: file not found:" << path);
return nullptr;
} }
Buffer b; Buffer b;
@@ -395,13 +396,15 @@ ALvoid* loadWAVFromFile(const SGPath& path, unsigned int& format, ALsizei& size,
fd = gzopen(ps.c_str(), "rb"); fd = gzopen(ps.c_str(), "rb");
#endif #endif
if (!fd) { if (!fd) {
throw sg_io_exception("loadWAVFromFile: unable to open file", path); SG_LOG(SG_IO, SG_DEV_ALERT, "loadWAVFromFile: unable to open file:" << path);
return nullptr;
} }
try { try {
loadWavFile(fd, &b); loadWavFile(fd, &b);
} catch (sg_exception& e) { } catch (sg_exception& e) {
throw sg_io_exception(e.getFormattedMessage() + "\nfor: " + path.str()); SG_LOG(SG_IO, SG_DEV_ALERT, "loadWAVFromFile:" << e.getFormattedMessage() << "\nfor: " << path);
return nullptr;
} }
ALvoid* data = b.data; ALvoid* data = b.data;
+4 -3
View File
@@ -813,13 +813,14 @@ bool SGSoundMgr::load( const std::string &samplepath,
auto data = simgear::loadWAVFromFile(samplepath, format, size, freqf, blocksz); auto data = simgear::loadWAVFromFile(samplepath, format, size, freqf, blocksz);
freq = (ALsizei)freqf; freq = (ALsizei)freqf;
if (data == nullptr) { if (!data) {
throw sg_io_exception("Failed to load wav file", sg_location(samplepath)); return false;
} }
if (format == AL_FORMAT_STEREO8 || format == AL_FORMAT_STEREO16) { if (format == AL_FORMAT_STEREO8 || format == AL_FORMAT_STEREO16) {
free(data); free(data);
throw sg_io_exception("Warning: STEREO files are not supported for 3D audio effects: " + samplepath); SG_LOG(SG_IO, SG_DEV_ALERT, "Warning: STEREO files are not supported for 3D audio effects: " << samplepath);
return false;
} }
*dbuf = (void *)data; *dbuf = (void *)data;
+5 -3
View File
@@ -84,7 +84,7 @@ SGXmlSound::~SGXmlSound()
_pitch.clear(); _pitch.clear();
} }
void bool
SGXmlSound::init( SGPropertyNode *root, SGXmlSound::init( SGPropertyNode *root,
SGPropertyNode *node, SGPropertyNode *node,
SGSampleGroup *sgrp, SGSampleGroup *sgrp,
@@ -315,8 +315,8 @@ SGXmlSound::init( SGPropertyNode *root,
string soundFileStr = node->getStringValue("path", ""); string soundFileStr = node->getStringValue("path", "");
_sample = new SGSoundSample(soundFileStr.c_str(), path); _sample = new SGSoundSample(soundFileStr.c_str(), path);
if (!_sample->file_path().exists()) { if (!_sample->file_path().exists()) {
throw sg_io_exception("XML sound: couldn't find file: '" + soundFileStr + "'"); SG_LOG(SG_SOUND, SG_WARN, "XML sound: couldn't find file: '" + soundFileStr + "'");
return; return false;
} }
_sample->set_relative_position( offset_pos ); _sample->set_relative_position( offset_pos );
@@ -328,6 +328,8 @@ SGXmlSound::init( SGPropertyNode *root,
_sample->set_volume( v ); _sample->set_volume( v );
_sample->set_pitch( p ); _sample->set_pitch( p );
_sgrp->add( _sample, _name ); _sgrp->add( _sample, _name );
return true;
} }
void void
+1 -1
View File
@@ -107,7 +107,7 @@ public:
* @param avionics A pointer to the pre-initialized avionics sample group. * @param avionics A pointer to the pre-initialized avionics sample group.
* @param path The path where the audio files remain. * @param path The path where the audio files remain.
*/ */
virtual void init( SGPropertyNode *root, virtual bool init( SGPropertyNode *root,
SGPropertyNode *child, SGPropertyNode *child,
SGSampleGroup *sgrp, SGSampleGroup *sgrp,
SGSampleGroup *avionics, SGSampleGroup *avionics,
+86
View File
@@ -0,0 +1,86 @@
#if 0
set bindings for action seperate from defintion ?
- in XML, especially aircraft XML
define actions from command / Nasal
add behaviours from Nasal
define keymapping
- manager of keybindings defined against actions?
- for a toggle or enum, define behaviour
- each key repeat cycles
- alternate key to go the other way (G/shift-G)
release bindings for momentary actions:
button up / key-up
send activate, release
send deactivate, release for 'alternate' action
#endif
void SGAction::setValueExpression()
{
// watch all the properties
}
void SGAction::setValueCondition()
{
//
}
void SGAction::updateProperties()
{
//
_node->setBoolValue("enabled", isEnabled());
switch (_type) {
case Momentary:
case Toggle:
_node->setBoolValue("value", getValue());
break;
case Enumerated:
if (!_valueEnumeration.empty()) {
// map to the string value
_node->setStringValue("value", _valueEnumeration.at(getValue()));
} else {
// set as an integer
_node->setIntValue("value", getValue());
}
}
// set description
}
bool SGAction::isEnabled()
{
if (_enableCondition) {
} else {
return _enabled;
}
updateProperties();
}
int SGAction::getValue()
{
if (type == Enumerated) {
if (_valueExpression) {
// invoke it
}
} else {
if (_valueCondition) {
return _valueCondition.test();
}
}
return _value;
}
// commands enable-action, disable-action