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