1
0
Fork 0

Allow replay of Continuous recordings if --load-tape is given a URL.

E.g. for --load-tape=http[s]://foo.com/foo/bar/wibble.fgtape, we download in
the background to a file called foo.com_[MD5]_wibble.fgtape, where [MD5] is an
8-character hash of /foo/bar.

We assume any existing file contains valid data and only download any remaining
data (by specifying an http Range header).

Also, when loading/downloading and replaying continuous recordings at startup,
we set the aircraft and airport from the recording.

src/Aircraft/replay.cxx
src/Aircraft/replay.hxx
    FGReplay::loadContinuousHeader()
        Loads properties from Continuous recording's header, distinguishing
        between failure due to incorrect header, or due to a truncated file.
    FGReplay::indexContinuousRecording()
        Contains Continuous recording indexing code, in a form that can be used
        in background while we are downloading.
    Added a mutex to protect m_continuous_in_time_to_frameinfo, which can now
    be modified in background as Continuous recording is downloaded.
src/Main/fg_init.cxx
src/Main/options.cxx
    fgOptLoadTape():
        Modified to handle --load-tape=<url>. We start download, and read
        the header before returning, so that we can force the FDM to use the
        recording's aircraft instead of the user's default. Limit recording
        download rate if /sim/replay/download-max-bytes-per-sec is set, by
        calling new filerequest->setMaxBytesPerSec().
This commit is contained in:
Julian Smith 2021-02-15 15:51:23 +00:00
parent 02e0d17dbc
commit f3679f121d
4 changed files with 418 additions and 159 deletions

View file

@ -1239,6 +1239,8 @@ FGReplay::replay( double time ) {
replayMessage(time);
std::lock_guard<std::mutex> lock(m_continuous_in_time_to_frameinfo_lock);
if (!m_continuous_in_time_to_frameinfo.empty()) {
// We are replaying a continuous recording.
//
@ -1615,6 +1617,7 @@ void FGReplay::replay(
double
FGReplay::get_start_time()
{
std::lock_guard<std::mutex> lock(m_continuous_in_time_to_frameinfo_lock);
if (!m_continuous_in_time_to_frameinfo.empty()) {
double ret = m_continuous_in_time_to_frameinfo.begin()->first;
SG_LOG(SG_SYSTEMS, SG_DEBUG,
@ -1645,6 +1648,7 @@ FGReplay::get_start_time()
double
FGReplay::get_end_time()
{
std::lock_guard<std::mutex> lock(m_continuous_in_time_to_frameinfo_lock);
if (!m_continuous_in_time_to_frameinfo.empty()) {
double ret = m_continuous_in_time_to_frameinfo.rbegin()->first;
SG_LOG(SG_SYSTEMS, SG_DEBUG,
@ -1824,142 +1828,225 @@ FGReplay::saveTape(const SGPropertyNode* Extra)
}
int FGReplay::loadContinuousHeader(const std::string& path, std::istream* in, SGPropertyNode* properties)
{
std::ifstream in0;
if (!in) {
in0.open(path);
in = &in0;
}
if (!*in) {
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to open path=" << path);
return +1;
}
std::vector<char> buffer(strlen( FlightRecorderFileMagic) + 1);
in->read(&buffer.front(), buffer.size());
SG_LOG(SG_SYSTEMS, SG_DEBUG, "in->gcount()=" << in->gcount() << " buffer.size()=" << buffer.size());
if ((size_t) in->gcount() != buffer.size()) {
// Further download is needed.
return +1;
}
if (strcmp(&buffer.front(), FlightRecorderFileMagic)) {
SG_LOG(SG_SYSTEMS, SG_DEBUG, "fgtape prefix doesn't match FlightRecorderFileMagic in path: " << path);
return -1;
}
bool ok = false;
try {
PropertiesRead(*in, properties);
ok = true;
}
catch (std::exception& e) {
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Failed to read Config properties in: " << path);
}
if (!ok) {
// Failed to read properties, so indicate that further download is needed.
return +1;
}
SG_LOG(SG_SYSTEMS, SG_BULK, "properties is:\n"
<< writePropertiesInline(properties, true /*write_all*/) << "\n");
return 0;
}
void FGReplay::indexContinuousRecording(const void* data, size_t numbytes)
{
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Indexing Continuous recording "
<< " data=" << data
<< " numbytes=" << numbytes
<< " m_continuous_indexing_pos=" << m_continuous_indexing_pos
<< " m_continuous_in_time_to_frameinfo.size()=" << m_continuous_in_time_to_frameinfo.size()
);
time_t t0 = time(NULL);
std::streampos original_pos = m_continuous_indexing_pos;
size_t original_num_frames = m_continuous_in_time_to_frameinfo.size();
// Reset any EOF because there might be new data.
m_continuous_indexing_in.clear();
for(;;)
{
SG_LOG(SG_SYSTEMS, SG_BULK, "reading frame."
<< " m_continuous_in.tellg()=" << m_continuous_in.tellg()
);
m_continuous_indexing_in.seekg(m_continuous_indexing_pos);
double sim_time;
m_continuous_indexing_in.read(reinterpret_cast<char*>(&sim_time), sizeof(sim_time));
SG_LOG(SG_SYSTEMS, SG_BULK, ""
<< " m_continuous_indexing_pos=" << m_continuous_indexing_pos
<< " m_continuous_indexing_in.tellg()=" << m_continuous_indexing_in.tellg()
<< " sim_time=" << sim_time
);
FGFrameInfo frameinfo;
frameinfo.offset = m_continuous_indexing_pos;
auto datas = m_continuous_in_config->getChildren("data");
SG_LOG(SG_SYSTEMS, SG_BULK, "datas.size()=" << datas.size());
for (auto data: datas) {
uint32_t length;
m_continuous_indexing_in.read(reinterpret_cast<char*>(&length), sizeof(length));
SG_LOG(SG_SYSTEMS, SG_BULK,
"m_continuous_in.tellg()=" << m_continuous_indexing_in.tellg()
<< " Skipping data_type=" << data->getStringValue()
<< " length=" << length
);
// Move forward <length> bytes.
m_continuous_indexing_in.seekg(length, std::ios_base::cur);
if (length) {
if (!strcmp(data->getStringValue(), "signals")) {
frameinfo.has_signals = true;
}
else if (!strcmp(data->getStringValue(), "multiplayer")) {
frameinfo.has_multiplayer = true;
++m_num_frames_multiplayer;
}
else if (!strcmp(data->getStringValue(), "extra-properties")) {
frameinfo.has_extra_properties = true;
++m_num_frames_extra_properties;
}
}
}
SG_LOG(SG_SYSTEMS, SG_BULK, ""
<< " pos=" << m_continuous_indexing_pos
<< " sim_time=" << sim_time
<< " m_num_frames_multiplayer=" << m_num_frames_multiplayer
<< " m_num_frames_extra_properties=" << m_num_frames_extra_properties
);
if (!m_continuous_indexing_in) {
// Failed to read a frame, e.g. because of EOF. Leave
// m_continuous_indexing_pos unchanged so we can try again at same
// starting position if recording is upated by background download.
//
SG_LOG(SG_SYSTEMS, SG_BULK, "m_continuous_indexing_in failed, giving up");
break;
}
// We have successfully read a frame, so add it to
// m_continuous_in_time_to_frameinfo[].
//
m_continuous_indexing_pos = m_continuous_indexing_in.tellg();
std::lock_guard<std::mutex> lock(m_continuous_in_time_to_frameinfo_lock);
m_continuous_in_time_to_frameinfo[sim_time] = frameinfo;
}
time_t t = time(NULL) - t0;
auto new_bytes = m_continuous_indexing_pos - original_pos;
auto num_frames = m_continuous_in_time_to_frameinfo.size();
auto num_new_frames = num_frames - original_num_frames;
if (num_new_frames) {
SG_LOG(SG_SYSTEMS, SG_ALERT, "Continuous recording: index updated:"
<< " num_frames=" << num_frames
<< " num_new_frames=" << num_new_frames
<< " new_bytes=" << new_bytes
);
}
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Indexed uncompressed recording."
<< " time taken=" << t << "s."
<< " num_new_frames=" << num_new_frames
<< " m_continuous_indexing_pos=" << m_continuous_indexing_pos
<< " m_continuous_in_time_to_frameinfo.size()=" << m_continuous_in_time_to_frameinfo.size()
<< " m_num_frames_multiplayer=" << m_num_frames_multiplayer
<< " m_num_frames_extra_properties=" << m_num_frames_extra_properties
);
// Probably don't need this lock because we're only reading
// m_continuous_in_time_to_frameinfo, and nothing else can be writing it.
//
std::lock_guard<std::mutex> lock(m_continuous_in_time_to_frameinfo_lock);
fgSetInt("/sim/replay/continuous-stats-num-frames", m_continuous_in_time_to_frameinfo.size());
fgSetInt("/sim/replay/continuous-stats-num-frames-extra-properties", m_num_frames_extra_properties);
fgSetInt("/sim/replay/continuous-stats-num-frames-multiplayer", m_num_frames_multiplayer);
if (!numbytes) {
SG_LOG(SG_SYSTEMS, SG_ALERT, "Continuous recording: indexing finished");
m_continuous_indexing_in.close();
}
}
/** Read a flight recorder tape with given filename from disk.
* Copies MetaData's "meta" node into MetaMeta out-param.
* Actual data and signal configuration is not read when in "Preview" mode.
*/
bool
FGReplay::loadTape(const SGPath& Filename, bool Preview, SGPropertyNode& MetaMeta)
FGReplay::loadTape(const SGPath& Filename, bool Preview, SGPropertyNode& MetaMeta, simgear::HTTP::FileRequest* filerequest)
{
SG_LOG(SG_SYSTEMS, SG_DEBUG, "loading Preview=" << Preview << " Filename=" << Filename);
{
/* Try to load as uncompressed Continuous recording first. */
std::ifstream in_preview;
std::ifstream& in(Preview ? in_preview : m_continuous_in);
in.open( Filename.str());
if (!in) {
SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to open Filename=" << Filename);
return false;
}
std::vector<char> buffer(strlen( FlightRecorderFileMagic) + 1);
in.read(&buffer.front(), buffer.size());
if (strcmp(&buffer.front(), FlightRecorderFileMagic)) {
SG_LOG(SG_SYSTEMS, SG_ALERT, "fgtape prefix doesn't match FlightRecorderFileMagic: '" << &buffer.front() << "'");
/* Try to load as uncompressed Continuous recording first. */
std::ifstream in_preview;
std::ifstream& in(Preview ? in_preview : m_continuous_in);
in.open(Filename.str());
if (!in) {
SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to open"
<< " Filename=" << Filename.str()
<< " in.is_open()=" << in.is_open()
);
return false;
}
m_continuous_in_config = new SGPropertyNode;
int e = loadContinuousHeader(Filename.str(), &in, m_continuous_in_config);
if (e == 0) {
SG_LOG(SG_SYSTEMS, SG_DEBUG, "m_continuous_in_config is:\n"
<< writePropertiesInline(m_continuous_in_config, true /*write_all*/) << "\n");
copyProperties(m_continuous_in_config->getNode("meta", 0, true), &MetaMeta);
if (Preview) {
in.close();
}
else {
SG_LOG(SG_SYSTEMS, SG_DEBUG, "fgtape is uncompressed: " << Filename);
m_continuous_in_config = new SGPropertyNode;
try {
PropertiesRead(in, m_continuous_in_config.get());
}
catch (std::exception& e) {
SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to read Config properties in: " << Filename);
in.close();
return false;
}
SG_LOG(SG_SYSTEMS, SG_DEBUG, "m_continuous_in_config is:\n"
<< writePropertiesInline(m_continuous_in_config, true /*write_all*/) << "\n");
copyProperties(m_continuous_in_config->getNode("meta", 0, true), &MetaMeta);
if (Preview) {
in.close();
return true;
}
m_pRecorder->reinit(m_continuous_in_config);
clear();
fillRecycler();
time_t t = time(NULL);
size_t pos = 0;
m_continuous_in_time_last = -1;
m_continuous_in_time_to_frameinfo.clear();
int num_frames_extra_properties = 0;
int num_frames_multiplayer = 0;
// Read entire recording and build up in-memory cache of simulator
// time to file offset, so we can handle random access.
//
// We also cache any frames that modify extra-properties.
//
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Indexing Continuous recording " << Filename);
for(;;)
{
SG_LOG(SG_SYSTEMS, SG_BULK, "reading frame."
<< " m_continuous_in.tellg()=" << m_continuous_in.tellg()
);
pos = m_continuous_in.tellg();
m_continuous_in.seekg(pos);
double sim_time;
m_continuous_in.read(reinterpret_cast<char*>(&sim_time), sizeof(sim_time));
SG_LOG(SG_SYSTEMS, SG_BULK,
"pos=" << pos
<< " m_continuous_in.tellg()=" << m_continuous_in.tellg()
<< " sim_time=" << sim_time
);
FGFrameInfo frameinfo;
frameinfo.offset = pos;
//bool frame_has_property_changes = false;
auto datas = m_continuous_in_config->getChildren("data");
SG_LOG(SG_SYSTEMS, SG_DEBUG, "datas.size()=" << datas.size());
for (auto data: datas) {
uint32_t length;
m_continuous_in.read(reinterpret_cast<char*>(&length), sizeof(length));
SG_LOG(SG_SYSTEMS, SG_BULK,
"m_continuous_in.tellg()=" << m_continuous_in.tellg()
<< " Skipping data_type=" << data->getStringValue()
<< " length=" << length
);
m_continuous_in.seekg(length, std::ios_base::cur);
if (length) {
if (!strcmp(data->getStringValue(), "signals")) {
frameinfo.has_signals = true;
}
else if (!strcmp(data->getStringValue(), "multiplayer")) {
frameinfo.has_multiplayer = true;
++num_frames_multiplayer;
}
else if (!strcmp(data->getStringValue(), "extra-properties")) {
frameinfo.has_extra_properties = true;
++num_frames_extra_properties;
}
}
}
SG_LOG(SG_SYSTEMS, SG_BULK, ""
<< " pos=" << pos
<< " sim_time=" << sim_time
<< " num_frames_multiplayer=" << num_frames_multiplayer
<< " num_frames_extra_properties=" << num_frames_extra_properties
);
if (!m_continuous_in) {
// EOF; we need to cope if last frame is incomplete, as
// this can easily happen if Flightgear was killed while
// recording.
break;
}
m_continuous_in_time_to_frameinfo[sim_time] = frameinfo;
}
t = time(NULL) - t;
SG_LOG(SG_SYSTEMS, SG_ALERT, "Indexed uncompressed recording"
<< ". time taken: " << t << "s"
<< ". recording size: " << pos
<< ". Number of frames: " << m_continuous_in_time_to_frameinfo.size()
<< ". num_frames_multiplayer: " << num_frames_multiplayer
<< ". num_frames_extra_properties: " << num_frames_extra_properties
);
fgSetInt("/sim/replay/continuous-stats-num-frames", m_continuous_in_time_to_frameinfo.size());
fgSetInt("/sim/replay/continuous-stats-num-frames-extra-properties", num_frames_extra_properties);
fgSetInt("/sim/replay/continuous-stats-num-frames-multiplayer", num_frames_multiplayer);
start(true /*NewTape*/);
return true;
}
}
m_pRecorder->reinit(m_continuous_in_config);
clear();
fillRecycler();
m_continuous_in_time_last = -1;
m_continuous_in_time_to_frameinfo.clear();
m_num_frames_extra_properties = 0;
m_num_frames_multiplayer = 0;
m_continuous_indexing_in.open(Filename.str());
m_continuous_indexing_pos = in.tellg();
SG_LOG(SG_SYSTEMS, SG_DEBUG, "filerequest=" << filerequest);
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Filename=" << Filename);
// Make an in-memory index of the recording.
if (filerequest) {
// Always call indexContinuousRecording once in case there is
// nothing to download.
indexContinuousRecording(nullptr, 1 /*Zero means EOF. */);
filerequest->setCallback( [this](const void* data, size_t numbytes) {
SG_LOG(SG_GENERAL, SG_BULK, "calling indexContinuousRecording() data=" << data << " numbytes=" << numbytes);
indexContinuousRecording(data, numbytes);
});
}
else {
indexContinuousRecording(nullptr, 0);
}
start(true /*NewTape*/);
return true;
}
// Not a continuous recording.
in.close();
if (filerequest) {
SG_LOG(SG_SYSTEMS, SG_DEBUG, "Cannot load Filename=" << Filename << " because it is download but not Continuous recording");
return false;
}
bool ok = true;
/* open input stream ********************************************/
/* Open as a gzipped Normal recording. ********************************************/
gzContainerReader input(Filename, FlightRecorderFileMagic);
if (input.eof() || !input.good())
{

View file

@ -34,6 +34,7 @@
#include <simgear/props/props.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/io/iostreams/gzcontainerfile.hxx>
#include <simgear/io/HTTPFileRequest.hxx>
#include <MultiPlayer/multiplaymgr.hxx>
@ -121,6 +122,27 @@ public:
bool saveTape(const SGPropertyNode* ConfigData);
bool loadTape(const SGPropertyNode* ConfigData);
// If filerequest is set, the local file is a Continuous recording and
// it might increase in size as downloading progresses, so we need to
// incrementally index the file until the file request has finished the
// download.
//
bool loadTape(
const SGPath& Filename,
bool Preview,
SGPropertyNode& MetaMeta,
simgear::HTTP::FileRequest* filerequest=nullptr
);
// Attempts to load Continuous recording header properties into
// <properties>. If in is null we use internal std::fstream, otherwise we
// use *in.
//
// Returns 0 on success, +1 if we may succeed after further download, or -1
// if recording is not a Continuous recording.
//
static int loadContinuousHeader(const std::string& path, std::istream* in, SGPropertyNode* properties);
private:
void clear();
@ -159,7 +181,16 @@ private:
bool listTapes(bool SameAircraftFilter, const SGPath& tapeDirectory);
bool saveTape(const SGPath& Filename, SGPropertyNode_ptr MetaData);
bool loadTape(const SGPath& Filename, bool Preview, SGPropertyNode& MetaMeta);
// Build up in-memory cache of simulator time to file offset, so we can
// handle random access.
//
// We also cache any frames that modify extra-properties.
//
// Can be called multiple times, e.g. if recording is being downlaoded.
//
void indexContinuousRecording(const void* data, size_t numbytes);
SGPropertyNode_ptr continuousWriteHeader(
std::ofstream& out,
const SGPath& path,
@ -215,9 +246,19 @@ private:
std::ifstream m_continuous_in;
bool m_continuous_in_multiplayer;
bool m_continuous_in_extra_properties;
std::mutex m_continuous_in_time_to_frameinfo_lock;
std::map<double, FGFrameInfo> m_continuous_in_time_to_frameinfo;
SGPropertyNode_ptr m_continuous_in_config;
double m_continuous_in_time_last;
std::ifstream m_continuous_indexing_in;
std::streampos m_continuous_indexing_pos;
// Only used for gathering statistics that are then written into
// properties.
//
int m_num_frames_extra_properties = 0;
int m_num_frames_multiplayer = 0;
// For writing uncompressed fgtape file.
SGPropertyNode_ptr m_continuous_out_config;

View file

@ -974,7 +974,7 @@ void fgCreateSubsystems(bool duringReset) {
throw sg_io_exception("Error loading materials file", mpath);
}
// may exist already due to GUI startup
// may exist already due to GUI startup or --load-tape=http...
if (!globals->get_subsystem<FGHTTPClient>()) {
globals->add_new_subsystem<FGHTTPClient>();
}

View file

@ -42,6 +42,8 @@
#include <string>
#include <sstream>
#include <simgear/io/HTTPClient.hxx>
#include <simgear/io/HTTPFileRequest.hxx>
#include <simgear/math/sg_random.h>
#include <simgear/props/props_io.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
@ -78,6 +80,7 @@
#include <Viewer/viewmgr.hxx>
#include <Environment/presets.hxx>
#include <Network/http/httpd.hxx>
#include <Network/HTTPClient.hxx>
#include "AircraftDirVisitorBase.hxx"
#include <osg/Version>
@ -1558,45 +1561,173 @@ fgOptSetProperty(const char* raw)
return ret ? FG_OPTIONS_OK : FG_OPTIONS_ERROR;
}
/* If <url> is a URL, return suitable name for downloaded file. */
static std::string urlToLocalPath(const char* url)
{
bool http = simgear::strutils::starts_with(url, "http://");
bool https = simgear::strutils::starts_with(url, "https://");
if (!http && !https) {
return "";
}
// e.g. http://fg.com/foo/bar/wibble.fgtape
const char* s2 = (http) ? url+7 : url+8; // fg.com/foo/bar/wibble.fgtape
const char* s3 = strchr(s2, '/'); // /foo/bar/wibble.fgtape
const char* s4 = (s3) ? strrchr(s3, '/') : NULL; // /wibble.fgtape
std::string path;
if (s3) path = std::string(s2, s3-s2); // fg.com
path += '_'; // fg.com_
if (s3 && s4 > s3) {
path += simgear::strutils::md5(s3, s4-s3).substr(0, 8);
path += '_'; // fg.com_12345678_
}
if (s4) path += (s4+1); // fg.com_12345678_wibble.fgtape
if (!simgear::strutils::ends_with(path, ".fgtape")) path += ".fgtape";
return path;
}
static int
fgOptLoadTape(const char* arg)
{
// load a flight recorder tape but wait until the fdm is initialized
class DelayedTapeLoader : SGPropertyChangeListener {
public:
DelayedTapeLoader( const char * tape ) :
_tape(SGPath::fromUtf8(tape))
{
SGPropertyNode_ptr n = fgGetNode("/sim/signals/fdm-initialized", true);
n->addChangeListener( this );
// load a flight recorder tape but wait until the fdm is initialized.
//
struct DelayedTapeLoader : SGPropertyChangeListener {
DelayedTapeLoader( const char * tape, simgear::HTTP::FileRequest* filerequest) :
_tape(SGPath::fromUtf8(tape)),
_filerequest(filerequest)
{
fgGetNode("/sim/signals/fdm-initialized", true)->addChangeListener( this );
}
virtual ~ DelayedTapeLoader() {}
virtual void valueChanged(SGPropertyNode * node)
{
if (!fgGetBool("/sim/signals/fdm-initialized")) {
return;
}
fgGetNode("/sim/signals/fdm-initialized", true)->removeChangeListener( this );
// tell the replay subsystem to load the tape
FGReplay* replay = globals->get_subsystem<FGReplay>();
assert(replay);
SGPropertyNode_ptr arg = new SGPropertyNode();
arg->setStringValue("tape", _tape.utf8Str() );
arg->setBoolValue( "same-aircraft", 0 );
if (!replay->loadTape(_tape, false /*preview*/, *arg, _filerequest)) {
// Force shutdown if we can't load tape specified on command-line.
SG_LOG(SG_GENERAL, SG_POPUP, "Exiting because unable to load fgtape: " << _tape.str());
flightgear::modalMessageBox("Exiting because unable to load fgtape", _tape.str(), "");
fgOSExit(1);
}
delete this; // commence suicide
}
private:
SGPath _tape;
simgear::HTTP::FileRequest* _filerequest;
};
SGPropertyNode_ptr properties(new SGPropertyNode);
simgear::HTTP::FileRequest* filerequest = nullptr;
std::string path = urlToLocalPath(arg);
if (path == "") {
// <arg> is a local file.
//
// Load the recording's header if it is a Continuous recording.
//
(void) FGReplay::loadContinuousHeader(arg, nullptr /*in*/, properties);
}
else {
// <arg> is a URL. Start download.
//
// Load the recording's header if it is a Continuous recording.
//
// This is a little messy - we need to create a FGHTTPClient subsystem
// in order to do the download, and we call its update() method
// directly in order to download at least the header.
//
const char* url = arg;
FGHTTPClient* http = globals->add_new_subsystem<FGHTTPClient>();
http->init();
filerequest = new simgear::HTTP::FileRequest(url, path, true /*append*/);
long max_download_speed = fgGetLong("/sim/replay/download-max-bytes-per-sec");
if (max_download_speed != 0) {
// Can be useful to limite download speed for testing background
// download.
//
SG_LOG(SG_GENERAL, SG_ALERT, "Limiting download speed"
<< " /sim/replay/download-max-bytes-per-sec=" << max_download_speed
);
filerequest->setMaxBytesPerSec(max_download_speed);
}
http->client()->makeRequest(filerequest);
SG_LOG(SG_GENERAL, SG_DEBUG, ""
<< " filerequest->responseCode()=" << filerequest->responseCode()
<< " filerequest->responseReason()=" << filerequest->responseReason()
);
// Load recording header, looping so that we wait for the initial
// portion of the recording to be downloaded. We give up after a fixed
// timeout.
//
time_t t0 = time(NULL);
for(;;) {
// Run http client's update() to download any pending data.
http->update(0);
// Try to load properties from recording header.
int e = FGReplay::loadContinuousHeader(path, nullptr /*in*/, properties);
if (e == 0) {
// Success. We leave <filerequest> active - it will carry
// on downloading when the main update loop gets going
// later. Hopefully the delay before that happens will not
// cause a server timeout.
//
break;
}
if (e == -1) {
SG_LOG(SG_GENERAL, SG_POPUP, "Not a Continuous recording: url=" << url << " local filename=" << path);
// Replay from URL only works with Continuous recordings.
return FG_OPTIONS_EXIT;
}
// If we get here, need to download some more.
if (time(NULL) - t0 > 30) {
SG_LOG(SG_GENERAL, SG_POPUP, "Timeout while reading downloaded recording from " << url << ". local path=" << path);
return FG_OPTIONS_EXIT;
}
sleep(1);
}
}
// Set aircraft from recording header if we loaded it above; this has to
// happen now, before the FDM is initialised. Also set the airport; we
// don't actually have to do this because the replay doesn't need terrain
// to work, but we might as well load the correct terrain.
//
std::string aircraft = properties->getStringValue("meta/aircraft-type");
std::string airport = properties->getStringValue("meta/closest-airport-id");
SG_LOG(SG_GENERAL, SG_ALERT, "From recording header: aircraft=" << aircraft << " airport=" << airport);
if (aircraft != "") {
// Force --aircraft and --airport options to use values from the
// recording.
//
Options::sharedInstance()->setOption("aircraft", aircraft);
}
if (airport != "") {
// Looks like setting --airport option doesn't work - we need to call
// fgOptAirport() directly.
//
Options::sharedInstance()->setOption("airport", airport);
fgOptAirport(airport.c_str());
}
virtual ~ DelayedTapeLoader() {}
virtual void valueChanged(SGPropertyNode * node)
{
node->removeChangeListener( this );
// tell the replay subsystem to load the tape
FGReplay* replay = globals->get_subsystem<FGReplay>();
SGPropertyNode_ptr arg = new SGPropertyNode();
arg->setStringValue("tape", _tape.utf8Str() );
arg->setBoolValue( "same-aircraft", 0 );
if (!replay->loadTape(arg)) {
// Force shutdown.
SG_LOG(SG_GENERAL, SG_POPUP, "Exiting because unable to load fgtape: " << _tape.str());
flightgear::modalMessageBox("Exiting because unable to load fgtape", _tape.str(), "");
fgOSExit(1);
}
delete this; // commence suicide
}
private:
SGPath _tape;
};
new DelayedTapeLoader(arg);
return FG_OPTIONS_OK;
// Arrange to load the recording after FDM has initialised.
new DelayedTapeLoader(path.c_str(), filerequest);
return FG_OPTIONS_OK;
}
static int fgOptDisableGUI(const char*)