]> git.mxchange.org Git - flightgear.git/blob - src/Aircraft/replay.cxx
NasalCanvas: Clean up and expose Element node ghost
[flightgear.git] / src / Aircraft / replay.cxx
1 // replay.cxx - a system to record and replay FlightGear flights
2 //
3 // Written by Curtis Olson, started July 2003.
4 // Updated by Thorsten Brehm, September 2011 and November 2012.
5 //
6 // Copyright (C) 2003  Curtis L. Olson  - http://www.flightgear.org/~curt
7 //
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License as
10 // published by the Free Software Foundation; either version 2 of the
11 // License, or (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful, but
14 // WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 // General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 //
22 // $Id$
23
24 #ifdef HAVE_CONFIG_H
25 #  include "config.h"
26 #endif
27
28 #include <float.h>
29
30 #include <simgear/constants.h>
31 #include <simgear/structure/exception.hxx>
32 #include <simgear/props/props_io.hxx>
33 #include <simgear/misc/gzcontainerfile.hxx>
34 #include <simgear/misc/sg_dir.hxx>
35 #include <simgear/misc/stdint.hxx>
36 #include <simgear/misc/strutils.hxx>
37
38 #include <Main/fg_props.hxx>
39
40 #include "replay.hxx"
41 #include "flightrecorder.hxx"
42
43 using std::deque;
44 using std::vector;
45 using simgear::gzContainerReader;
46 using simgear::gzContainerWriter;
47
48 #if 1
49     #define MY_SG_DEBUG SG_DEBUG
50 #else
51     #define MY_SG_DEBUG SG_ALERT
52 #endif
53
54 /** Magic string to verify valid FG flight recorder tapes. */
55 static const char* const FlightRecorderFileMagic = "FlightGear Flight Recorder Tape";
56
57 namespace ReplayContainer
58 {
59     enum Type
60     {
61         Invalid    = -1,
62         Header     = 0, /**< Used for initial file header (fixed identification string). */
63         MetaData   = 1, /**< XML data / properties with arbitrary data, such as description, aircraft type, ... */
64         Properties = 2, /**< XML data describing the recorded flight recorder properties.
65                              Format is identical to flight recorder XML configuration. Also contains some
66                              extra data to verify flight recorder consistency. */
67         RawData    = 3  /**< Actual binary data blobs (the recorder's tape).
68                              One "RawData" blob is used for each resolution. */
69     };
70 }
71
72 /**
73  * Constructor
74  */
75
76 FGReplay::FGReplay() :
77     sim_time(0),
78     last_mt_time(0.0),
79     last_lt_time(0.0),
80     last_msg_time(0),
81     last_replay_state(0),
82     m_high_res_time(60.0),
83     m_medium_res_time(600.0),
84     m_low_res_time(3600.0),
85     m_medium_sample_rate(0.5), // medium term sample rate (sec)
86     m_long_sample_rate(5.0),   // long term sample rate (sec)
87     m_pRecorder(new FGFlightRecorder("replay-config"))
88 {
89 }
90
91 /**
92  * Destructor
93  */
94
95 FGReplay::~FGReplay()
96 {
97     clear();
98
99     delete m_pRecorder;
100     m_pRecorder = NULL;
101 }
102
103 /**
104  * Clear all internal buffers.
105  */
106 void
107 FGReplay::clear()
108 {
109     while ( !short_term.empty() )
110     {
111         m_pRecorder->deleteRecord(short_term.front());
112         short_term.pop_front();
113     }
114     while ( !medium_term.empty() )
115     {
116         m_pRecorder->deleteRecord(medium_term.front());
117         medium_term.pop_front();
118     }
119     while ( !long_term.empty() )
120     {
121         m_pRecorder->deleteRecord(long_term.front());
122         long_term.pop_front();
123     }
124     while ( !recycler.empty() )
125     {
126         m_pRecorder->deleteRecord(recycler.front());
127         recycler.pop_front();
128     }
129
130     // clear messages belonging to old replay session
131     fgGetNode("/sim/replay/messages", 0, true)->removeChildren("msg", false);
132 }
133
134 /** 
135  * Initialize the data structures
136  */
137
138 void
139 FGReplay::init()
140 {
141     disable_replay  = fgGetNode("/sim/replay/disable",      true);
142     replay_master   = fgGetNode("/sim/replay/replay-state", true);
143     replay_time     = fgGetNode("/sim/replay/time",         true);
144     replay_time_str = fgGetNode("/sim/replay/time-str",     true);
145     replay_looped   = fgGetNode("/sim/replay/looped",       true);
146     speed_up        = fgGetNode("/sim/speed-up",            true);
147
148     // alias to keep backward compatibility
149     fgGetNode("/sim/freeze/replay-state", true)->alias(replay_master);
150
151     reinit();
152 }
153
154 /** 
155  * Reset replay queues.
156  */
157
158 void
159 FGReplay::reinit()
160 {
161     sim_time = 0.0;
162     last_mt_time = 0.0;
163     last_lt_time = 0.0;
164     last_msg_time = 0.0;
165
166     // Flush queues
167     clear();
168     m_pRecorder->reinit();
169
170     m_high_res_time   = fgGetDouble("/sim/replay/buffer/high-res-time",    60.0);
171     m_medium_res_time = fgGetDouble("/sim/replay/buffer/medium-res-time", 600.0); // 10 mins
172     m_low_res_time    = fgGetDouble("/sim/replay/buffer/low-res-time",   3600.0); // 1 h
173     // short term sample rate is as every frame
174     m_medium_sample_rate = fgGetDouble("/sim/replay/buffer/medium-res-sample-dt", 0.5); // medium term sample rate (sec)
175     m_long_sample_rate   = fgGetDouble("/sim/replay/buffer/low-res-sample-dt",    5.0); // long term sample rate (sec)
176
177     fillRecycler();
178     loadMessages();
179
180     replay_master->setIntValue(0);
181     disable_replay->setBoolValue(0);
182     replay_time->setDoubleValue(0);
183     replay_time_str->setStringValue("");
184 }
185
186 /** 
187  * Bind to the property tree
188  */
189
190 void
191 FGReplay::bind()
192 {
193 }
194
195 /** 
196  *  Unbind from the property tree
197  */
198
199 void
200 FGReplay::unbind()
201 {
202     // nothing to unbind
203 }
204
205 void
206 FGReplay::fillRecycler()
207 {
208     // Create an estimated nr of required ReplayData objects
209     // 120 is an estimated maximum frame rate.
210     int estNrObjects = (int) ((m_high_res_time*120) + (m_medium_res_time*m_medium_sample_rate) +
211                              (m_low_res_time*m_long_sample_rate));
212     for (int i = 0; i < estNrObjects; i++)
213     {
214         FGReplayData* r = m_pRecorder->createEmptyRecord();
215         if (r)
216             recycler.push_back(r);
217         else
218         {
219             SG_LOG(SG_SYSTEMS, SG_ALERT, "ReplaySystem: Out of memory!");
220         }
221     }
222 }
223
224 static void
225 printTimeStr(char* pStrBuffer,double _Time, bool ShowDecimal=true)
226 {
227     if (_Time<0)
228         _Time = 0;
229     unsigned int Time = _Time*10;
230     unsigned int h = Time/36000;
231     unsigned int m = (Time % 36000)/600;
232     unsigned int s = (Time % 600)/10;
233     unsigned int d = Time % 10;
234
235     int len;
236     if (h>0)
237         len = sprintf(pStrBuffer,"%u:%02u:%02u",h,m,s);
238     else
239         len = sprintf(pStrBuffer,"%u:%02u",m,s);
240
241     if (len < 0)
242     {
243         *pStrBuffer = 0;
244         return;
245     }
246
247     if (ShowDecimal)
248         sprintf(&pStrBuffer[len],".%u",d);
249 }
250
251 void
252 FGReplay::guiMessage(const char* message)
253 {
254     fgSetString("/sim/messages/copilot", message);
255 }
256
257 void
258 FGReplay::loadMessages()
259 {
260     // load messages
261     replay_messages.clear();
262     simgear::PropertyList msgs = fgGetNode("/sim/replay/messages", true)->getChildren("msg");
263
264     for (simgear::PropertyList::iterator it = msgs.begin();it != msgs.end();++it)
265     {
266         const char* msgText = (*it)->getStringValue("text", "");
267         const double msgTime = (*it)->getDoubleValue("time", -1.0);
268         const char* msgSpeaker = (*it)->getStringValue("speaker", "pilot");
269         if ((msgText[0] != 0)&&(msgTime >= 0))
270         {
271             FGReplayMessages data;
272             data.sim_time = msgTime;
273             data.message = msgText;
274             data.speaker = msgSpeaker;
275             replay_messages.push_back(data);
276         }
277     }
278     current_msg = replay_messages.begin();
279 }
280
281 void
282 FGReplay::replayMessage(double time)
283 {
284     if (time < last_msg_time)
285     {
286         current_msg = replay_messages.begin();
287     }
288
289     // check if messages have to be triggered
290     while ((current_msg != replay_messages.end())&&
291            (time >= current_msg->sim_time))
292     {
293         // don't trigger messages when too long ago (fast-forward/skipped replay)
294         if (time - current_msg->sim_time < 3.0)
295         {
296             fgGetNode("/sim/messages", true)->getNode(current_msg->speaker, true)->setStringValue(current_msg->message);
297         }
298         ++current_msg;
299     }
300     last_msg_time = time;
301 }
302
303 /** Start replay session
304  */
305 bool
306 FGReplay::start(bool NewTape)
307 {
308     // freeze the fdm, resume from sim pause
309     double StartTime = get_start_time();
310     double EndTime = get_end_time();
311     was_finished_already = false;
312     fgSetDouble("/sim/replay/start-time", StartTime);
313     fgSetDouble("/sim/replay/end-time",   EndTime);
314     char StrBuffer[30];
315     printTimeStr(StrBuffer,StartTime,false);
316     fgSetString("/sim/replay/start-time-str", StrBuffer);
317     printTimeStr(StrBuffer,EndTime,false);
318     fgSetString("/sim/replay/end-time-str",   StrBuffer);
319
320     unsigned long buffer_elements =  short_term.size()+medium_term.size()+long_term.size();
321     fgSetDouble("/sim/replay/buffer-size-mbyte",
322                 buffer_elements*m_pRecorder->getRecordSize() / (1024*1024.0));
323     if ((fgGetBool("/sim/freeze/master"))||
324         (0 == replay_master->getIntValue()))
325         guiMessage("Replay active. 'Esc' to stop.");
326     fgSetBool  ("/sim/freeze/master",     0);
327     fgSetBool  ("/sim/freeze/clock",      0);
328     if (0 == replay_master->getIntValue())
329     {
330         replay_master->setIntValue(1);
331         if (NewTape)
332         {
333             // start replay at initial time, when loading a new tape
334             replay_time->setDoubleValue(StartTime);
335         }
336         else
337         {
338             // start replay at "loop interval" when starting instant replay
339             replay_time->setDoubleValue(-1);
340         }
341         replay_time_str->setStringValue("");
342     }
343     loadMessages();
344
345     return true;
346 }
347
348 /** 
349  *  Update the saved data
350  */
351
352 void
353 FGReplay::update( double dt )
354 {
355     int current_replay_state = last_replay_state;
356     timingInfo.clear();
357     stamp("begin");
358
359     if ( disable_replay->getBoolValue() )
360     {
361         if (fgGetBool("/sim/freeze/master",false)||
362             fgGetBool("/sim/freeze/clock",false))
363         {
364             // unpause - disable the replay system in next loop
365             fgSetBool("/sim/freeze/master",false);
366             fgSetBool("/sim/freeze/clock",false);
367             last_replay_state = 1;
368         }
369         else
370         if ((replay_master->getIntValue() != 3)||
371             (last_replay_state == 3))
372         {
373             // disable the replay system
374             current_replay_state = replay_master->getIntValue();
375             replay_master->setIntValue(0);
376             replay_time->setDoubleValue(0);
377             replay_time_str->setStringValue("");
378             disable_replay->setBoolValue(0);
379             speed_up->setDoubleValue(1.0);
380             speed_up->setDoubleValue(1.0);
381             if (fgGetBool("/sim/replay/mute",false))
382             {
383                 fgSetBool("/sim/sound/enabled",true);
384                 fgSetBool("/sim/replay/mute",false);
385             }
386             guiMessage("Replay stopped. Your controls!");
387         }
388     }
389
390     int replay_state = replay_master->getIntValue();
391     if ((replay_state == 0)&&
392         (last_replay_state > 0))
393     {
394         if (current_replay_state == 3)
395         {
396             // take control at current replay position ("My controls!").
397             // May need to uncrash the aircraft here :)
398             fgSetBool("/sim/crashed", false);
399         }
400         else
401         {
402             // normal replay exit, restore most recent frame
403             replay(DBL_MAX);
404         }
405
406         // replay is finished
407         last_replay_state = replay_state;
408         return;
409     }
410
411     // remember recent state
412     last_replay_state = replay_state;
413
414     switch(replay_state)
415     {
416         case 0:
417             // replay inactive, keep recording
418             break;
419         case 1: // normal replay
420         case 3: // prepare to resume normal flight at current replay position 
421         {
422             // replay active
423             double current_time = replay_time->getDoubleValue();
424             bool ResetTime = (current_time<=0.0);
425             if (ResetTime)
426             {
427                 // initialize start time
428                 double startTime = get_start_time();
429                 double endTime = get_end_time();
430                 fgSetDouble( "/sim/replay/start-time", startTime );
431                 fgSetDouble( "/sim/replay/end-time", endTime );
432                 double duration = 0;
433                 if (replay_looped->getBoolValue())
434                     fgGetDouble("/sim/replay/duration");
435                 if( duration && (duration < (endTime - startTime)) ) {
436                     current_time = endTime - duration;
437                 } else {
438                     current_time = startTime;
439                 }
440             }
441             bool IsFinished = replay( replay_time->getDoubleValue() );
442             if (IsFinished)
443             {
444                 if (!was_finished_already)
445                 {
446                     guiMessage("End of tape. 'Esc' to return.");
447                     was_finished_already = true;
448                 }
449                 current_time = (replay_looped->getBoolValue()) ? -1 : get_end_time()+0.01;
450             }
451             else
452             {
453                 current_time += dt * speed_up->getDoubleValue();
454                 was_finished_already = false;
455             }
456             replay_time->setDoubleValue(current_time);
457             char StrBuffer[30];
458             printTimeStr(StrBuffer,current_time);
459             replay_time_str->setStringValue((const char*)StrBuffer);
460
461             // when time skipped (looped replay), trigger listeners to reset views etc
462             if (ResetTime)
463                 replay_master->setIntValue(replay_state);
464
465             return; // don't record the replay session 
466         }
467         case 2: // normal replay operation
468             return; // don't record the replay session
469         default:
470             throw sg_range_exception("unknown FGReplay state");
471     }
472
473     // flight recording
474
475     // sanity check, don't collect data if FDM data isn't good
476     if ((!fgGetBool("/sim/fdm-initialized", false))||(dt==0.0))
477         return;
478
479     {
480         double new_sim_time = sim_time + dt * speed_up->getDoubleValue();
481         // don't record multiple records with the same timestamp (or go backwards in time)
482         if (new_sim_time <= sim_time)
483         {
484             SG_LOG(SG_SYSTEMS, SG_ALERT, "ReplaySystem: Time warp detected!");
485             return;
486         }
487         sim_time = new_sim_time;
488     }
489
490     FGReplayData* r = record(sim_time);
491     if (!r)
492     {
493         SG_LOG(SG_SYSTEMS, SG_ALERT, "ReplaySystem: Out of memory!");
494         return;
495     }
496
497     // update the short term list
498     short_term.push_back( r );
499     FGReplayData *st_front = short_term.front();
500     
501     if (!st_front)
502     {
503         SG_LOG(SG_SYSTEMS, SG_ALERT, "ReplaySystem: Inconsistent data!");
504     }
505
506     if ( sim_time - st_front->sim_time > m_high_res_time )
507     {
508         while ( sim_time - st_front->sim_time > m_high_res_time )
509         {
510             st_front = short_term.front();
511             recycler.push_back(st_front);
512             short_term.pop_front();
513         }
514
515         // update the medium term list
516         if ( sim_time - last_mt_time > m_medium_sample_rate )
517         {
518             last_mt_time = sim_time;
519             st_front = short_term.front();
520             medium_term.push_back( st_front );
521             short_term.pop_front();
522
523             FGReplayData *mt_front = medium_term.front();
524             if ( sim_time - mt_front->sim_time > m_medium_res_time )
525             {
526                 while ( sim_time - mt_front->sim_time > m_medium_res_time )
527                 {
528                     mt_front = medium_term.front();
529                     recycler.push_back(mt_front);
530                     medium_term.pop_front();
531                 }
532                 // update the long term list
533                 if ( sim_time - last_lt_time > m_long_sample_rate )
534                 {
535                     last_lt_time = sim_time;
536                     mt_front = medium_term.front();
537                     long_term.push_back( mt_front );
538                     medium_term.pop_front();
539
540                     FGReplayData *lt_front = long_term.front();
541                     if ( sim_time - lt_front->sim_time > m_low_res_time )
542                     {
543                         while ( sim_time - lt_front->sim_time > m_low_res_time )
544                         {
545                             lt_front = long_term.front();
546                             recycler.push_back(lt_front);
547                             long_term.pop_front();
548                         }
549                     }
550                 }
551             }
552         }
553     }
554
555 #if 0
556     cout << "short term size = " << short_term.size()
557          << "  time = " << sim_time - short_term.front().sim_time
558          << endl;
559     cout << "medium term size = " << medium_term.size()
560          << "  time = " << sim_time - medium_term.front().sim_time
561          << endl;
562     cout << "long term size = " << long_term.size()
563          << "  time = " << sim_time - long_term.front().sim_time
564          << endl;
565 #endif
566    //stamp("point_finished");
567 }
568
569 FGReplayData*
570 FGReplay::record(double time)
571 {
572     FGReplayData* r = NULL;
573
574     if (recycler.size())
575     {
576         r = recycler.front();
577         recycler.pop_front();
578     }
579
580     return m_pRecorder->capture(time, r);
581 }
582
583 /** 
584  * interpolate a specific time from a specific list
585  */
586 void
587 FGReplay::interpolate( double time, const replay_list_type &list)
588 {
589     // sanity checking
590     if ( list.size() == 0 )
591     {
592         // handle empty list
593         return;
594     } else if ( list.size() == 1 )
595     {
596         // handle list size == 1
597         replay(time, list[0]);
598         return;
599     }
600
601     unsigned int last = list.size() - 1;
602     unsigned int first = 0;
603     unsigned int mid = ( last + first ) / 2;
604
605     bool done = false;
606     while ( !done )
607     {
608         // cout << "  " << first << " <=> " << last << endl;
609         if ( last == first ) {
610             done = true;
611         } else if ( list[mid]->sim_time < time && list[mid+1]->sim_time < time ) {
612             // too low
613             first = mid;
614             mid = ( last + first ) / 2;
615         } else if ( list[mid]->sim_time > time && list[mid+1]->sim_time > time ) {
616             // too high
617             last = mid;
618             mid = ( last + first ) / 2;
619         } else {
620             done = true;
621         }
622     }
623
624     replay(time, list[mid+1], list[mid]);
625 }
626
627 /** 
628  *  Replay a saved frame based on time, interpolate from the two
629  *  nearest saved frames.
630  *  Returns true when replay sequence has finished, false otherwise.
631  */
632
633 bool
634 FGReplay::replay( double time ) {
635     // cout << "replay: " << time << " ";
636     // find the two frames to interpolate between
637     double t1, t2;
638
639     replayMessage(time);
640
641     if ( short_term.size() > 0 ) {
642         t1 = short_term.back()->sim_time;
643         t2 = short_term.front()->sim_time;
644         if ( time > t1 ) {
645             // replay the most recent frame
646             replay( time, short_term.back() );
647             // replay is finished now
648             return true;
649         } else if ( time <= t1 && time >= t2 ) {
650             interpolate( time, short_term );
651         } else if ( medium_term.size() > 0 ) {
652             t1 = short_term.front()->sim_time;
653             t2 = medium_term.back()->sim_time;
654             if ( time <= t1 && time >= t2 )
655             {
656                 replay(time, medium_term.back(), short_term.front());
657             } else {
658                 t1 = medium_term.back()->sim_time;
659                 t2 = medium_term.front()->sim_time;
660                 if ( time <= t1 && time >= t2 ) {
661                     interpolate( time, medium_term );
662                 } else if ( long_term.size() > 0 ) {
663                     t1 = medium_term.front()->sim_time;
664                     t2 = long_term.back()->sim_time;
665                     if ( time <= t1 && time >= t2 )
666                     {
667                         replay(time, long_term.back(), medium_term.front());
668                     } else {
669                         t1 = long_term.back()->sim_time;
670                         t2 = long_term.front()->sim_time;
671                         if ( time <= t1 && time >= t2 ) {
672                             interpolate( time, long_term );
673                         } else {
674                             // replay the oldest long term frame
675                             replay(time, long_term.front());
676                         }
677                     }
678                 } else {
679                     // replay the oldest medium term frame
680                     replay(time, medium_term.front());
681                 }
682             }
683         } else {
684             // replay the oldest short term frame
685             replay(time, short_term.front());
686         }
687     } else {
688         // nothing to replay
689         return true;
690     }
691     return false;
692 }
693
694 /** 
695  * given two FGReplayData elements and a time, interpolate between them
696  */
697 void
698 FGReplay::replay(double time, FGReplayData* pCurrentFrame, FGReplayData* pOldFrame)
699 {
700     m_pRecorder->replay(time,pCurrentFrame,pOldFrame);
701 }
702
703 double
704 FGReplay::get_start_time()
705 {
706     if ( long_term.size() > 0 )
707     {
708         return long_term.front()->sim_time;
709     } else if ( medium_term.size() > 0 )
710     {
711         return medium_term.front()->sim_time;
712     } else if ( short_term.size() )
713     {
714         return short_term.front()->sim_time;
715     } else
716     {
717         return 0.0;
718     }
719 }
720
721 double
722 FGReplay::get_end_time()
723 {
724     if ( short_term.size() )
725     {
726         return short_term.back()->sim_time;
727     } else
728     {
729         return 0.0;
730     } 
731 }
732
733 /** Save raw replay data in a separate container */
734 static bool
735 saveRawReplayData(gzContainerWriter& output, const replay_list_type& ReplayData, size_t RecordSize)
736 {
737     // get number of records in this stream
738     size_t Count = ReplayData.size();
739
740     // write container header for raw data
741     if (!output.writeContainerHeader(ReplayContainer::RawData, Count * RecordSize))
742     {
743         SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to save replay data. Cannot write data container. Disk full?");
744         return false;
745     }
746
747     // write the raw data (all records in the given list)
748     replay_list_type::const_iterator it = ReplayData.begin();
749     size_t CheckCount = 0;
750     while ((it != ReplayData.end())&&
751            !output.fail())
752     {
753         const FGReplayData* pRecord = *it++;
754         output.write((char*)pRecord, RecordSize);
755         CheckCount++;
756     }
757
758     // Did we really write as much as we intended?
759     if (CheckCount != Count)
760     {
761         SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to save replay data. Expected to write " << Count << " records, but wrote " << CheckCount);
762         return false;
763     }
764
765     SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Saved " << CheckCount << " records of size " << RecordSize);
766     return !output.fail();
767 }
768
769 /** Load raw replay data from a separate container */
770 static bool
771 loadRawReplayData(gzContainerReader& input, FGFlightRecorder* pRecorder, replay_list_type& ReplayData, size_t RecordSize)
772 {
773     size_t Size = 0;
774     simgear::ContainerType Type = ReplayContainer::Invalid;
775
776     // write container header for raw data
777     if (!input.readContainerHeader(&Type, &Size))
778     {
779         SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to load replay data. Missing data container.");
780         return false;
781     }
782     else
783     if (Type != ReplayContainer::RawData)
784     {
785         SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to load replay data. Expected data container, got " << Type);
786         return false;
787     }
788
789     // read the raw data
790     size_t Count = Size / RecordSize;
791     SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Loading replay data. Container size is " << Size << ", record size " << RecordSize <<
792            ", expected record count " << Count << ".");
793
794     size_t CheckCount = 0;
795     for (CheckCount=0; (CheckCount<Count)&&(!input.eof()); ++CheckCount)
796     {
797         FGReplayData* pBuffer = pRecorder->createEmptyRecord();
798         input.read((char*) pBuffer, RecordSize);
799         ReplayData.push_back(pBuffer);
800     }
801
802     // did we get all we have hoped for?
803     if (CheckCount != Count)
804     {
805         if (input.eof())
806         {
807             SG_LOG(SG_SYSTEMS, SG_ALERT, "Unexpected end of file.");
808         }
809         SG_LOG(SG_SYSTEMS, SG_ALERT, "Failed to load replay data. Expected " << Count << " records, but got " << CheckCount);
810         return false;
811     }
812
813     SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Loaded " << CheckCount << " records of size " << RecordSize);
814     return true;
815 }
816
817 /** Write flight recorder tape with given filename and meta properties to disk */
818 bool
819 FGReplay::saveTape(const char* Filename, SGPropertyNode* MetaDataProps)
820 {
821     bool ok = true;
822
823     /* open output stream *******************************************/
824     gzContainerWriter output(Filename, FlightRecorderFileMagic);
825     if (!output.good())
826     {
827         SG_LOG(SG_SYSTEMS, SG_ALERT, "Cannot open file" << Filename);
828         return false;
829     }
830
831     /* write meta data **********************************************/
832     ok &= output.writeContainer(ReplayContainer::MetaData, MetaDataProps);
833
834     /* write flight recorder configuration **************************/
835     SGPropertyNode_ptr Config;
836     if (ok)
837     {
838         Config = new SGPropertyNode();
839         m_pRecorder->getConfig(Config.get());
840         ok &= output.writeContainer(ReplayContainer::Properties, Config.get());
841     }
842
843     /* write raw data ***********************************************/
844     if (Config)
845     {
846         size_t RecordSize = Config->getIntValue("recorder/record-size", 0);
847         SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Total signal count: " <<  Config->getIntValue("recorder/signal-count", 0)
848                << ", record size: " << RecordSize);
849         if (ok)
850             ok &= saveRawReplayData(output, short_term,  RecordSize);
851         if (ok)
852             ok &= saveRawReplayData(output, medium_term, RecordSize);
853         if (ok)
854             ok &= saveRawReplayData(output, long_term,   RecordSize);
855         Config = 0;
856     }
857
858     /* done *********************************************************/
859     output.close();
860
861     return ok;
862 }
863
864 /** Write flight recorder tape to disk. User/script command. */
865 bool
866 FGReplay::saveTape(const SGPropertyNode* ConfigData)
867 {
868     const char* tapeDirectory = fgGetString("/sim/replay/tape-directory", "");
869     const char* aircraftType  = fgGetString("/sim/aircraft", "unknown");
870
871     SGPropertyNode_ptr myMetaData = new SGPropertyNode();
872     SGPropertyNode* meta = myMetaData->getNode("meta", 0, true);
873
874     // add some data to the file - so we know for which aircraft/version it was recorded
875     meta->setStringValue("aircraft-type",           aircraftType);
876     meta->setStringValue("aircraft-description",    fgGetString("/sim/description", ""));
877     meta->setStringValue("aircraft-fdm",            fgGetString("/sim/flight-model", ""));
878     meta->setStringValue("closest-airport-id",      fgGetString("/sim/airport/closest-airport-id", ""));
879     const char* aircraft_version = fgGetString("/sim/aircraft-version", "");
880     if (aircraft_version[0]==0)
881         aircraft_version = "(undefined)";
882     meta->setStringValue("aircraft-version", aircraft_version);
883
884     // add information on the tape's recording duration
885     double Duration = get_end_time()-get_start_time();
886     meta->setDoubleValue("tape-duration", Duration);
887     char StrBuffer[30];
888     printTimeStr(StrBuffer, Duration, false);
889     meta->setStringValue("tape-duration-str", StrBuffer);
890
891     // add simulator version
892     copyProperties(fgGetNode("/sim/version", 0, true), meta->getNode("version", 0, true));
893     if (ConfigData->getNode("user-data"))
894     {
895         copyProperties(ConfigData->getNode("user-data"), meta->getNode("user-data", 0, true));
896     }
897
898     // store replay messages
899     copyProperties(fgGetNode("/sim/replay/messages", 0, true), myMetaData->getNode("messages", 0, true));
900
901     // generate file name (directory + aircraft type + date + time + suffix)
902     SGPath p(tapeDirectory);
903     p.append(aircraftType);
904     p.concat("-");
905     time_t calendar_time = time(NULL);
906     struct tm *local_tm;
907     local_tm = localtime( &calendar_time );
908     char time_str[256];
909     strftime( time_str, 256, "%Y%02m%02d-%02H%02M%02S", local_tm);
910     p.concat(time_str);
911     p.concat(".fgtape");
912
913     bool ok = true;
914     // make sure we're not overwriting something
915     if (p.exists())
916     {
917         // same timestamp!?
918         SG_LOG(SG_SYSTEMS, SG_ALERT, "Error, flight recorder tape file with same name already exists.");
919         ok = false;
920     }
921
922     if (ok)
923         ok &= saveTape(p.c_str(), myMetaData.get());
924
925     if (ok)
926         guiMessage("Flight recorder tape saved successfully!");
927     else
928         guiMessage("Failed to save tape! See log output.");
929
930     return ok;
931 }
932
933 /** Read a flight recorder tape with given filename from disk and return meta properties.
934  * Actual data and signal configuration is not read when in "Preview" mode.
935  */
936 bool
937 FGReplay::loadTape(const char* Filename, bool Preview, SGPropertyNode* UserData)
938 {
939     bool ok = true;
940
941     /* open input stream ********************************************/
942     gzContainerReader input(Filename, FlightRecorderFileMagic);
943     if (input.eof() || !input.good())
944     {
945         SG_LOG(SG_SYSTEMS, SG_ALERT, "Cannot open file " << Filename);
946         ok = false;
947     }
948
949     SGPropertyNode_ptr MetaDataProps = new SGPropertyNode();
950
951     /* read meta data ***********************************************/
952     if (ok)
953     {
954         char* MetaData = NULL;
955         size_t Size = 0;
956         simgear::ContainerType Type = ReplayContainer::Invalid;
957         if (!input.readContainer(&Type, &MetaData, &Size) || Size<1)
958         {
959             SG_LOG(SG_SYSTEMS, SG_ALERT, "File not recognized. This is not a valid FlightGear flight recorder tape: " << Filename
960                     << ". Invalid meta data.");
961             ok = false;
962         }
963         else
964         if (Type != ReplayContainer::MetaData)
965         {
966             SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Invalid header. Container type " << Type);
967             SG_LOG(SG_SYSTEMS, SG_ALERT, "File not recognized. This is not a valid FlightGear flight recorder tape: " << Filename);
968             ok = false;
969         }
970         else
971         {
972             try
973             {
974                 readProperties(MetaData, Size-1, MetaDataProps);
975                 copyProperties(MetaDataProps->getNode("meta", 0, true), UserData);
976             } catch (const sg_exception &e)
977             {
978               SG_LOG(SG_SYSTEMS, SG_ALERT, "Error reading flight recorder tape: " << Filename
979                      << ", XML parser message:" << e.getFormattedMessage());
980               ok = false;
981             }
982         }
983
984         if (MetaData)
985         {
986             //printf("%s\n", MetaData);
987             free(MetaData);
988             MetaData = NULL;
989         }
990     }
991
992     /* read flight recorder configuration **************************/
993     if ((ok)&&(!Preview))
994     {
995         SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Loading flight recorder data...");
996         char* ConfigXML = NULL;
997         size_t Size = 0;
998         simgear::ContainerType Type = ReplayContainer::Invalid;
999         if (!input.readContainer(&Type, &ConfigXML, &Size) || Size<1)
1000         {
1001             SG_LOG(SG_SYSTEMS, SG_ALERT, "File not recognized. This is not a valid FlightGear flight recorder tape: " << Filename
1002                    << ". Invalid configuration container.");
1003             ok = false;
1004         }
1005         else
1006         if ((!ConfigXML)||(Type != ReplayContainer::Properties))
1007         {
1008             SG_LOG(SG_SYSTEMS, SG_ALERT, "File not recognized. This is not a valid FlightGear flight recorder tape: " << Filename
1009                    << ". Unexpected container type, expected \"properties\".");
1010             ok = false;
1011         }
1012
1013         SGPropertyNode_ptr Config = new SGPropertyNode();
1014         if (ok)
1015         {
1016             try
1017             {
1018                 readProperties(ConfigXML, Size-1, Config);
1019             } catch (const sg_exception &e)
1020             {
1021               SG_LOG(SG_SYSTEMS, SG_ALERT, "Error reading flight recorder tape: " << Filename
1022                      << ", XML parser message:" << e.getFormattedMessage());
1023               ok = false;
1024             }
1025             if (ok)
1026             {
1027                 // reconfigure the recorder - and wipe old data (no longer matches the current recorder)
1028                 m_pRecorder->reinit(Config);
1029                 clear();
1030                 fillRecycler();
1031             }
1032         }
1033
1034         if (ConfigXML)
1035         {
1036             free(ConfigXML);
1037             ConfigXML = NULL;
1038         }
1039
1040         /* read raw data ***********************************************/
1041         if (ok)
1042         {
1043             size_t RecordSize = m_pRecorder->getRecordSize();
1044             size_t OriginalSize = Config->getIntValue("recorder/record-size", 0);
1045             // check consistency - ugly things happen when data vs signals mismatch
1046             if ((OriginalSize != RecordSize)&&
1047                 (OriginalSize != 0))
1048             {
1049                 ok = false;
1050                 SG_LOG(SG_SYSTEMS, SG_ALERT, "Error: Data inconsistency. Flight recorder tape has record size " << RecordSize
1051                        << ", expected size was " << OriginalSize << ".");
1052             }
1053
1054             if (ok)
1055                 ok &= loadRawReplayData(input, m_pRecorder, short_term,  RecordSize);
1056             if (ok)
1057                 ok &= loadRawReplayData(input, m_pRecorder, medium_term, RecordSize);
1058             if (ok)
1059                 ok &= loadRawReplayData(input, m_pRecorder, long_term,   RecordSize);
1060
1061             // restore replay messages
1062             if (ok)
1063             {
1064                 copyProperties(MetaDataProps->getNode("messages", 0, true),
1065                                fgGetNode("/sim/replay/messages", 0, true));
1066             }
1067             sim_time = get_end_time();
1068             // TODO we could (re)store these too
1069             last_mt_time = last_lt_time = sim_time;
1070         }
1071         /* done *********************************************************/
1072     }
1073
1074     input.close();
1075
1076     if (!Preview)
1077     {
1078         if (ok)
1079         {
1080             guiMessage("Flight recorder tape loaded successfully!");
1081             start(true);
1082         }
1083         else
1084             guiMessage("Failed to load tape. See log output.");
1085     }
1086
1087     return ok;
1088 }
1089
1090 /** List available tapes in current directory.
1091  * Limits to tapes matching current aircraft when SameAircraftFilter is enabled.
1092  */
1093 bool
1094 FGReplay::listTapes(bool SameAircraftFilter, const SGPath& tapeDirectory)
1095 {
1096     const std::string& aircraftType = simgear::strutils::uppercase(fgGetString("/sim/aircraft", "unknown"));
1097
1098     // process directory listing of ".fgtape" files
1099     simgear::Dir dir(tapeDirectory);
1100     simgear::PathList list =  dir.children(simgear::Dir::TYPE_FILE, ".fgtape");
1101
1102     SGPropertyNode* TapeList = fgGetNode("/sim/replay/tape-list", true);
1103     TapeList->removeChildren("tape", false);
1104     int Index = 0;
1105     size_t l = aircraftType.size();
1106     for (simgear::PathList::iterator it = list.begin(); it!=list.end(); ++it)
1107     {
1108         SGPath file(it->file());
1109         std::string name(file.base());
1110         if ((!SameAircraftFilter)||
1111             (0==simgear::strutils::uppercase(name).compare(0,l, aircraftType)))
1112         {
1113             TapeList->getNode("tape", Index++, true)->setStringValue(name);
1114         }
1115     }
1116
1117     return true;
1118 }
1119
1120 /** Load a flight recorder tape from disk. User/script command. */
1121 bool
1122 FGReplay::loadTape(const SGPropertyNode* ConfigData)
1123 {
1124     SGPath tapeDirectory(fgGetString("/sim/replay/tape-directory", ""));
1125
1126     // see if shall really load the file - or just obtain the meta data for preview
1127     bool Preview = ConfigData->getBoolValue("preview", 0);
1128
1129     // file/tape to be loaded
1130     std::string tape = ConfigData->getStringValue("tape", "");
1131
1132     if (tape.empty())
1133     {
1134         if (!Preview)
1135             return listTapes(ConfigData->getBoolValue("same-aircraft", 0), tapeDirectory);
1136         return true;
1137     }
1138     else
1139     {
1140         SGPropertyNode* UserData = fgGetNode("/sim/gui/dialogs/flightrecorder/preview", true);
1141         tapeDirectory.append(tape);
1142         tapeDirectory.concat(".fgtape");
1143         SG_LOG(SG_SYSTEMS, MY_SG_DEBUG, "Checking flight recorder file " << tapeDirectory << ", preview: " << Preview);
1144         return loadTape(tapeDirectory.c_str(), Preview, UserData);
1145     }
1146 }