vrpn  07.33
Virtual Reality Peripheral Network
vrpn_FileConnection.C
Go to the documentation of this file.
1 // vrpn_FileConnection.C
2 
3 // {{{ includes and defines
4 
5 #include "vrpn_FileConnection.h"
6 
7 #ifndef _WIN32_WCE
8 #include <fcntl.h> // for SEEK_SET
9 #endif
10 #include <limits.h> // for LONG_MAX, LONG_MIN
11 #include <stdio.h> // for NULL, fprintf, stderr, etc
12 #include <string.h> // for memcpy
13 
14 // Include vrpn_Shared.h _first_ to avoid conflicts with sys/time.h
15 // and netinet/in.h and ...
16 #include "vrpn_Shared.h" // for timeval, etc
17 #if !(defined(_WIN32) && defined(VRPN_USE_WINSOCK_SOCKETS))
18 #include <netinet/in.h> // for ntohl
19 #endif
20 
21 // Global variable used to indicate whether File Connections should
22 // pre-load all of their records into memory when opened. This is the
23 // default behavior, but fails on very large files that eat up all
24 // of the memory.
25 // This is used to initialize the data member for each new file connection
26 // so that it will do what is expected. This setting is stored per file
27 // connection so that a given file connection will behave consistently.
28 
30 
31 // Global variable used to indicate whether File Connections should
32 // keep already-read messages stored in memory. If not, then we have
33 // to re-load the file starting at the beginning on rewind.
34 // This is used to initialize the data member for each new file connection
35 // so that it will do what is expected. This setting is stored per file
36 // connection so that a given file connection will behave consistently.
37 
39 
40 // Global variable used to indicate whether File Connections should
41 // play through all system messages and get to the first user message
42 // when opened or reset to the beginning. This defaults to "true".
43 // User code should set this
44 // to "false" before calling vrpn_open_client_connection() or creating
45 // a new vrpn_File_Connection object if it wants that file connection
46 // to not skip the messages. The value is only checked at connection creation
47 // time;
48 // the connection behaves consistently once created. Leaving this true
49 // can help with offsets in time that happen at the beginning of files.
50 
52 
53 #define CHECK(x) \
54  if (x == -1) return -1
55 
56 #include "vrpn_Log.h" // for vrpn_Log
57 
58 struct timeval;
59 
60 // }}}
61 // {{{ constructor
62 
64  const char *local_in_logfile_name,
65  const char *local_out_logfile_name)
66  : vrpn_Connection(local_in_logfile_name, local_out_logfile_name, NULL, NULL)
67  , d_controllerId(register_sender("vrpn File Controller"))
68  , d_set_replay_rate_type(register_message_type("vrpn_File set_replay_rate"))
69  , d_reset_type(register_message_type("vrpn_File reset"))
70  , d_play_to_time_type(register_message_type("vrpn_File play_to_time"))
71  , d_fileName(NULL)
72  , d_file(NULL)
73  , d_logHead(NULL)
74  , d_logTail(NULL)
75  , d_currentLogEntry(NULL)
76  , d_startEntry(NULL)
79 {
80  d_last_told.tv_sec = 0;
81  d_last_told.tv_usec = 0;
82 
83  // Because we are a file connection, our status should be CONNECTED
84  // Later set this to BROKEN if there is a problem opening/reading the file.
85  if (d_endpoints[0] == NULL) {
86  fprintf(stderr, "vrpn_File_Connection::vrpn_File_Connection(): NULL "
87  "zeroeth endpoint\n");
88  }
89  else {
92  }
93 
94  // If we are preloading, then we must accumulate messages.
95  if (d_preload) {
96  d_accumulate = true;
97  }
98 
99  // These are handlers for messages that may be sent from a
100  // vrpn_File_Controller object that may attach itself to us.
106 
107  // necessary to initialize properly in mainloop()
108  d_last_time.tv_usec = d_last_time.tv_sec = 0;
109 
110  d_fileName = vrpn_copy_file_name(station_name);
111  if (!d_fileName) {
112  fprintf(stderr, "vrpn_File_Connection: Out of memory!\n");
114  return;
115  }
116 
117  d_file = fopen(d_fileName, "rb");
118  if (!d_file) {
119  fprintf(stderr, "vrpn_File_Connection: "
120  "Could not open file \"%s\".\n",
121  d_fileName);
123  return;
124  }
125 
126  // Read the cookie from the file. It will print an error message if it
127  // can't read it, so we just pass the broken status on up the chain.
128  if (read_cookie() < 0) {
130  return;
131  }
132 
133  // If we are supposed to preload the entire file into memory buffers,
134  // then keep reading until we get to the end. Otherwise, just read the
135  // first message to get things going.
136  if (d_preload) {
137  while (!read_entry()) {
138  }
139  }
140  else {
141  read_entry();
142  }
143 
144  // Initialize the "current message" pointer to the first log-file
145  // entry that was read, and set the start time for the file and
146  // the current time to the one in this message.
147  if (d_logHead) {
152  d_earliest_user_time.tv_sec = d_earliest_user_time.tv_usec = 0;
154  d_highest_user_time.tv_sec = d_highest_user_time.tv_usec = 0;
156  }
157  else {
158  fprintf(stderr, "vrpn_File_Connection: Can't read first message\n");
160  return;
161  }
162 
163  // This is useful to play the initial system messages
164  // (the sender/type ones) automatically. These might not be
165  // time synched so if we don't play them automatically they
166  // can mess up playback if their timestamps are later than
167  // the first user message.
170  if (d_currentLogEntry) {
173  }
174  }
175 
176  // Add this to the list of known connections.
177  vrpn_ConnectionManager::instance().addConnection(this, station_name);
178 }
179 
180 // }}}
181 // {{{ play_to_user_message
182 
183 // Advances through the file, calling callbacks, up until
184 // a user message (type >= 0) is encountered)
186 {
187  // As long as the current message is a system message, play it.
188  // Also stop if we get to the end of the file.
189  while (d_currentLogEntry && (d_currentLogEntry->data.type < 0)) {
190  playone();
191  }
192 
193  // we advance d_time one ahead because they're may be
194  // a large gap in the file (forward or backwards) between
195  // the last system message and first user one
196  if (d_currentLogEntry) {
198  }
199 }
200 
201 // }}}
202 // {{{ destructor
203 
204 // virtual
206 {
207  vrpn_LOGLIST *np;
208 
209  // Remove myself from the "known connections" list
210  // (or the "anonymous connections" list).
212 
213  close_file();
214  delete[] d_fileName;
215  d_fileName = NULL;
216 
217  // Delete any messages that are in memory, and their data buffers.
218  while (d_logHead) {
219  np = d_logHead->next;
220  if (d_logHead->data.buffer) {
221  delete[](char *)d_logHead -> data.buffer;
222  }
223  delete d_logHead;
224  d_logHead = np;
225  }
226 }
227 
228 // }}}
229 // {{{ jump_to_time
230 
231 // newtime is an elapsed time from the start of the file
232 int vrpn_File_Connection::jump_to_time(vrpn_float64 newtime)
233 {
234  return jump_to_time(vrpn_MsecsTimeval(newtime * 1000));
235 }
236 
237 // If the time is before our current time (or there is no current
238 // time) then reset back to the beginning. Whether or not we did
239 // that, search forwards until we get to or past the time we are
240 // searching for.
241 // newtime is an elapsed time from the start of the file
243 {
246  }
247  else {
248  d_time = vrpn_TimevalSum(d_start_time, newtime);
249  } // XXX get rid of this option - dtm
250 
251  // If the time is earlier than where we are, or if we have
252  // run past the end (no current entry), jump back to
253  // the beginning of the file before searching.
254  if (!d_currentLogEntry ||
256  reset();
257  }
258 
259  // Search forwards, as needed. Do not play the messages as they are
260  // passed, just skip over them until we get to a message that has a
261  // time greater than or equal to the one we are looking for. That is,
262  // one whose time is not less than ours.
264  int ret = advance_currentLogEntry();
265  if (ret != 0) {
266  return 0; // Didn't get where we were going!
267  }
268  }
269 
270  return 1; // Got where we were going!
271 }
272 
273 int vrpn_File_Connection::jump_to_filetime(timeval absolute_time)
274 {
276  return jump_to_time(
277  vrpn_TimevalDiff(absolute_time, d_earliest_user_time));
278  }
279  else {
280  return jump_to_time(vrpn_TimevalDiff(absolute_time, d_start_time));
281  } // XX get rid of this option - dtm
282 }
283 
284 // }}}
285 // {{{ vrpn_File_Connection::FileTime_Accumulator
287  : d_replay_rate(1.0)
288 {
289  d_filetime_accum_since_last_playback.tv_sec = 0;
290  d_filetime_accum_since_last_playback.tv_usec = 0;
291 
292  d_time_of_last_accum.tv_sec = 0;
293  d_time_of_last_accum.tv_usec = 0;
294 }
295 
297  const timeval &now_time)
298 {
299  timeval &accum = d_filetime_accum_since_last_playback;
300  timeval &last_accum = d_time_of_last_accum;
301 
302  accum // updated elapsed filetime
303  = vrpn_TimevalSum( // summed with previous elapsed time
304  accum,
305  vrpn_TimevalScale( // scaled by replay rate
306  vrpn_TimevalDiff( // elapsed wallclock time
307  now_time, d_time_of_last_accum),
308  d_replay_rate));
309  last_accum = now_time; // wallclock time of this whole mess
310 }
311 
313  vrpn_float32 new_rate)
314 {
315  timeval now_time;
316  vrpn_gettimeofday(&now_time, NULL);
317  accumulate_to(now_time);
318 
319  d_replay_rate = new_rate;
320  // fprintf(stderr, "Set replay rate!\n");
321 }
322 
324  const timeval &now_time)
325 {
326  // this function is confusing. It doesn't appear to do anything.
327  // (I added the next three lines. did I delete what was previously there?)
328  d_filetime_accum_since_last_playback.tv_sec = 0;
329  d_filetime_accum_since_last_playback.tv_usec = 0;
330  d_time_of_last_accum = now_time;
331 }
332 // {{{ end vrpn_File_Connection::FileTime_Accumulator
333 // }}}
334 
335 // }}}
336 // {{{ mainloop
337 
338 // {{{ -- comment
339 
340 // [juliano 10/11/99] the problem described below is now fixed
341 // [juliano 8/26/99] I believe there to be a bug in mainloop.
342 //
343 // Essentially, the computation of end_time is sample-and-hold
344 // integration, using the value of d_rate at the end of the integration
345 // window. Consider the case where you are happily playing from your
346 // file, then pause it. Pausing is accomplished by setting d_rate to
347 // zero. Then, 10 minutes later, you unpause by setting d_rate to one.
348 //
349 // Now, you have an integration window of 10 minutes. The way it's
350 // currently implemented, you will compute end_time as d_rate * 10
351 // minutes. This is obviously not correct.
352 //
353 // So, what is the ideal way this would work? Well, if you had continuous
354 // integration and asynchronous events, then the new message would come at
355 // the same time as it would if we executed the same scenerio with a VCR.
356 //
357 // Since our value of d_rate changes by impulses at specified times (it
358 // only changes from inside set_replay_rate) sample-and-hold integration
359 // can still be used to do exact computation. What we need to do is
360 // accumulate "virtial time", and compare with it. By "virtual time" I
361 // mean the analog to VITC timestamps on a videotape in a VCR. We will
362 // accumulate it in d_virtual_time_elapsed_since_last_event. The units
363 // need to have enough precision that we won't run into any of the
364 // problems that have already been worked around in the code below. It
365 // represents the amount of time elapsed sice d_last_time was last set.
366 //
367 // set_replay_rate will do sample-and-hold integration over the window
368 // from d_last_time to the result of vrpn_gettimeofday(), and it will
369 // accumulate the result into d_virtual_time_elapsed_since_last_event.
370 // then it will set d_last_time to the result of vrpn_gettimeofday().
371 //
372 // mainloop will also do sample-and-hold integration over the window
373 // [d_last_time:vrpn_gettimeofday()], and accumulate the result into
374 // d_virtual_time_elapsed_since_last_event. Then it will compute end_time
375 // by adding d_time and d_virtual_time_elapsed_since_last_event. iff an
376 // event is played from the file, d_last_time will be set to now_time and
377 // d_virtual_time_elapsed_since_last_event will be zero'd.
378 
379 // }}}
380 
381 // virtual
382 int vrpn_File_Connection::mainloop(const timeval * /*timeout*/)
383 {
384  // XXX timeout ignored for now, needs to be added
385 
386  timeval now_time;
387  vrpn_gettimeofday(&now_time, NULL);
388 
389  if ((d_last_time.tv_sec == 0) && (d_last_time.tv_usec == 0)) {
390  // If first iteration, consider 0 time elapsed
391  d_last_time = now_time;
393  return 0;
394  }
395 
396  // now_time: current wallclock time (on method entry)
397  // d_last_time: wallclock time of last call to mainloop
398  // (juliano-8/26/99) NO! It is the time the
399  // wallclock read (at the top of mainloop) when the
400  // last event was played back from the file.
401  // If you call mainloop frequently enough,
402  // these are not necessarily the same!
403  // (may call mainloop too soon and then no event
404  // is played back from the file)
405  // d_time: current time in file
406  // end_time: computed time in file
407  // d_rate: wallclock -> fileclock rate scale factor
408  // goal: compute end_time, then advance to it
409  //
410  // scale elapsed time by d_rate (rate of replay);
411  // this gives us the time to advance (skip_time)
412  // our clock to (next_time).
413  // -- see note above!
414  //
415  // const timeval real_elapsed_time // amount of ellapsed wallclock time
416  // = vrpn_TimevalDiff( now_time, d_last_time );
417  // const timeval skip_time // scale it by d_rate
418  // = vrpn_TimevalScale( real_elapsed_time, d_rate );
419  // const timeval end_time // add it to the last file-time
420  // = vrpn_TimevalSum( d_time, skip_time );
421  //
422  // ------ new way of calculating end_time ------------
423 
425  const timeval end_time =
427 
428  // (winston) Had to add need_to_play() because at fractional rates
429  // (even just 1/10th) the d_time didn't accumulate properly
430  // because tiny intervals after scaling were too small
431  // for a timeval to represent (1us minimum).
432  //
433  // (juliano-8/26/99) if ((end_time - timestamp of next event) < 1us)
434  // then you have run out of precision in the struct timeval when
435  // need_to_play differences those two timevals. I.e., they
436  // appear to be the same time.
437  // need_to_play will return n:n>1 only if this difference
438  // is non-zero.
439  //
440  // (juliano-8/25/99) need_to_play is not a boolean function!
441  // it returns n:n>0 if you need to play
442  // n=0 if the timevals compare equal
443  // n=-1 if there was an error reading the next event
444  // from the log file
445  const int need_to_play_retval = need_to_play(end_time);
446 
447  if (need_to_play_retval > 0) {
448  d_last_time = now_time;
450  const int rv = play_to_filetime(end_time);
451  return rv;
452  }
453  else if (need_to_play_retval == 0) {
454  // (winston) we don't set d_last_time so that we can more
455  // accurately measure the (larger) interval next time around
456  //
457  // (juliano-8/26/99) sounds right. Only set d_last_time
458  // if you actually played some event from the file.
459  // You may get here if you have your data in more than one
460  // file, and are trying to play back from the files in lockstep.
461  // The tracker group does this to run the hybrid tracking
462  // algorithm on both an inertial data file and a hiball
463  // tracker file that were recorded with synchronized clocks.
464  return 0;
465  }
466  else {
467  // return something to indicate there was an error
468  // reading the file
469  return -1;
470 
471  // an error occurred while reading the next event from the file
472  // let's close the connection.
473  // XXX(jj) is this the right thing to do?
474  // XXX(jj) for now, let's leave it how it was
475  // XXX(jj) come back to this and do it right
476  /*
477  fprintf( stderr, "vrpn_File_Connection::mainloop(): error
478  reading "
479  "next event from file. Skipping to end of file. "
480  "XXX Please edit this function and fix it. It should
481  probably"
482  " close the connection right here and now.\n");
483  d_last_time = now_time;
484  d_filetime_accum.reset_at_time( now_time );
485  return play_to_filetime(end_time);
486  */
487  }
488 }
489 
490 // }}}
491 // {{{ need_to_play
492 
493 // checks if there is at least one log entry that occurs
494 // between the current d_time and the given filetime
495 //
496 // [juliano 9/24/99] the above comment is almost right
497 // the upper bound of the interval is not open,
498 // but closed at time_we_want_to_play_to.
499 //
500 // this function checks if the next message to play back
501 // from the stream file has a timestamp LESSTHAN OR EQUAL TO
502 // the argument to this function (which is the time that we
503 // wish to play to). If it does, then a pos value is returned
504 //
505 // you can pause playback of a streamfile by ceasing to increment
506 // the value that is passed to this function. However, if the next
507 // message in the streamfile has the same timestamp as the previous
508 // one, it will get played anyway. Pause will not be achieved until
509 // all such messages have been played.
510 //
511 // Beware: make sure you put the correct timestamps on individual
512 // messages when recording them in chunks (batches)
513 // to a time to a streamfile.
514 //
515 int vrpn_File_Connection::need_to_play(timeval time_we_want_to_play_to)
516 {
517  // This read_entry() call may be useful to test the state, but
518  // it should be the case that d_currentLogEntry is non-NULL except
519  // when we are at the end. This is because read_entry() and the
520  // constructor now both read the next one in when they are finished.
521  if (!d_currentLogEntry) {
522  int retval = read_entry();
523  if (retval < 0) {
524  return -1;
525  } // error reading from file
526  if (retval > 0) {
527  return 0;
528  } // end of file; nothing to replay
530  d_logTail; // If read_entry() returns 0, this will be non-NULL
531  }
532 
534 
535  // [juliano 9/24/99] is this right?
536  // this is a ">" test, not a ">=" test
537  // consequently, this function keeps returning true until the
538  // next message is timestamped greater. So, if a group of
539  // messages share a timestamp, you cannot pause streamfile
540  // replay anywhere inside the group.
541  //
542  // this is true, but do you ever want to pause in the middle of
543  // such a group? This was a problem prior to fixing the
544  // timeval overflow bug, but now that it's fixed, (who cares?)
545 
546  return vrpn_TimevalGreater(time_we_want_to_play_to, header.msg_time);
547 }
548 
549 // }}}
550 // {{{ play_to_time and play_to_filetime
551 
552 // plays to an elapsed end_time (in seconds)
553 int vrpn_File_Connection::play_to_time(vrpn_float64 end_time)
554 {
555  return play_to_time(vrpn_MsecsTimeval(end_time * 1000));
556 }
557 
558 // plays to an elapsed end_time
560 {
562  return play_to_filetime(
564  }
565  else {
566  return play_to_filetime(vrpn_TimevalSum(d_start_time, end_time));
567  }
568 }
569 
570 // plays all entries between d_time and end_filetime
571 // returns -1 on error, 0 on success
572 int vrpn_File_Connection::play_to_filetime(const timeval end_filetime)
573 {
574  vrpn_uint32 playback_this_iteration = 0;
575 
576  if (vrpn_TimevalGreater(d_time, end_filetime)) {
577  // end_filetime is BEFORE d_time (our current time in the stream)
578  // so, we need to go backwards in the stream
579  // currently, this is implemented by
580  // * rewinding the stream to the beginning
581  // * playing log messages one at a time until we get to end_filetime
582  reset();
583  }
584 
585  int ret;
586  while ((ret = playone_to_filetime(end_filetime)) == 0) {
587  // * you get here ONLY IF playone_to_filetime returned 0
588  // * that means that it played one entry
589 
590  playback_this_iteration++;
591  if ((get_Jane_value() > 0) &&
592  (playback_this_iteration >= get_Jane_value())) {
593  // Early exit from the loop
594  // Don't reset d_time to end_filetime
595  return 0;
596  }
597  }
598 
599  if (ret == 1) {
600  // playone_to_filetime finished or EOF no error for us
601  // Set log position to the exact requested ending time,
602  // don't leave it at the last log entry time
603  d_time = end_filetime;
604  ret = 0;
605  }
606 
607  return ret;
608 }
609 
610 // }}}
611 // {{{ rest
612 
613 // returns 1 if we're at the EOF, -1 on error
615 {
616  if (d_currentLogEntry) {
617  return 0;
618  }
619  // read from disk if not in memory
620  int ret = read_entry();
621  if (ret == 0) {
623  d_logTail; // If read_entry() returns zero, this will be non-NULL
624  }
625 
626  return ret;
627 }
628 
629 // plays at most one entry which comes before end_filetime
630 // returns
631 // -1 on error (including EOF, call eof() to test)
632 // 0 for normal result (played one entry)
634 {
635  static const timeval tvMAX = {LONG_MAX, 999999L};
636 
637  int ret = playone_to_filetime(tvMAX);
638  if (ret != 0) {
639  // consider a 1 return from playone_to_filetime() to be
640  // an error since it should never reach tvMAX
641  return -1;
642  }
643  else {
644  return 0;
645  }
646 }
647 
648 // plays at most one entry which comes before end_filetime
649 // returns
650 // -1 on error (including EOF, call eof() to test)
651 // 0 for normal result (played one entry)
652 // 1 if we hit end_filetime
654 {
655  vrpn_Endpoint *endpoint = d_endpoints[0];
656  timeval now;
657  int retval;
658 
659  // If we don't have a currentLogEntry, then we've gone past the end of the
660  // file.
661  if (!d_currentLogEntry) {
662  return 1;
663  }
664 
666 
667  if (vrpn_TimevalGreater(header.msg_time, end_filetime)) {
668  // there are no entries to play after the current
669  // but before end_filetime
670  return 1;
671  }
672 
673  // TCH July 2001
674  // XXX A big design decision: do we re-log messages exactly,
675  // or do we mark them with the time they were played back?
676  // Maybe this should be switchable, but the latter is what
677  // I need yesterday.
678  vrpn_gettimeofday(&now, NULL);
679  retval = endpoint->d_inLog->logIncomingMessage(
680  header.payload_len, now, header.type, header.sender, header.buffer);
681  if (retval) {
682  fprintf(stderr, "Couldn't log \"incoming\" message during replay!\n");
683  return -1;
684  }
685 
686  // advance current file position
687  d_time = header.msg_time;
688 
689  // Handle this log entry
690  if (header.type >= 0) {
691 #ifdef VERBOSE
692  printf("vrpn_FC: Msg Sender (%s), Type (%s), at (%ld:%ld)\n",
693  endpoint->other_senders[header.sender].name,
694  endpoint->other_types[header.type].name, header.msg_time.tv_sec,
695  header.msg_time.tv_usec);
696 #endif
697  if (endpoint->local_type_id(header.type) >= 0) {
698  if (do_callbacks_for(endpoint->local_type_id(header.type),
699  endpoint->local_sender_id(header.sender),
700  header.msg_time, header.payload_len,
701  header.buffer)) {
702  return -1;
703  }
704  }
705  }
706  else { // system handler
707 
708  if (header.type != vrpn_CONNECTION_UDP_DESCRIPTION) {
709  if (doSystemCallbacksFor(header, endpoint)) {
710  fprintf(stderr, "vrpn_File_Connection::playone_to_filename: "
711  "Nonzero system return.\n");
712  return -1;
713  }
714  }
715  }
716 
717  return advance_currentLogEntry();
718 }
719 
720 // Advance to next entry. If there is no next entry, and if we have
721 // not preloaded, then try to read one in.
723 {
724  // If we don't have a currentLogEntry, then we've gone past the end of the
725  // file.
726  if (!d_currentLogEntry) {
727  return 1;
728  }
729 
731  if (!d_currentLogEntry && !d_preload) {
732  int retval = read_entry();
733  if (retval != 0) {
734  return -1; // error reading from file or EOF
735  }
737  d_logTail; // If read_entry() returns zero, this will be non-NULL
738  }
739 
740  return 0;
741 }
742 
744 {
745  return vrpn_TimevalMsecs(get_length()) / 1000;
746 }
747 
748 // virtual
750 {
751  timeval len = {0, 0};
752 
756  }
757 
759  return len;
760 }
761 
763 {
765  return d_earliest_user_time;
766 }
767 
769 {
771  return d_highest_user_time;
772 }
773 
775 {
776  timeval high = {0, 0};
777  timeval low = {LONG_MAX, 999999L};
778 
779  // Remember where we were when we asked this question
780  bool retval = store_stream_bookmark();
781  if (retval == false) {
782 #ifdef VERBOSE
783  printf("vrpn_File_Connection::find_superlative_user_times: didn't "
784  "successfully save bookmark.\n");
785 #endif
786  return;
787  }
788 
789  // Go to the beginning of the file and then run through all
790  // of the messages to find the one with the lowest/highest value
791  reset();
792  do {
793  if (d_currentLogEntry && (d_currentLogEntry->data.type >= 0)) {
796  }
799  }
800  }
801  } while (d_currentLogEntry && (advance_currentLogEntry() == 0));
802 
803  // We have our value. Set it and go back where
804  // we came from, but don't play the records along
805  // the way.
806  retval = return_to_bookmark();
807  if (retval == false) {
808  // oops. we've really screwed things up.
809  fprintf(stderr, "vrpn_File_Connection::find_superlative_user_times "
810  "messed up the location in the file stream.\n");
811  reset();
812  return;
813  }
814 
815  if (high.tv_sec != LONG_MIN) // we found something
816  {
817  d_highest_user_time = high;
819  }
820 #ifdef VERBOSE
821  else {
822  fprintf( stderr, "vrpn_File_Connection::find_superlative_user_times: did not find a highest-time user message\n"
823  }
824 #endif
825 
826  if (low.tv_sec != LONG_MAX) // we found something
827  {
828  d_earliest_user_time = low;
829  d_earliest_user_time_valid = true;
830  }
831 #ifdef VERBOSE
832  else {
833  fprintf( stderr, "vrpn_File_Connection::find_superlative_user_times: did not find an earliest user message\n"
834  }
835 #endif
836 
837 } // end find_superlative_user_times
838 
840 {
841  valid = false;
842  file_pos = -1;
843  oldTime.tv_sec = 0;
844  oldTime.tv_usec = 0;
845  oldCurrentLogEntryPtr = NULL;
846  oldCurrentLogEntryCopy = NULL;
847 }
848 
850 {
851  if (oldCurrentLogEntryCopy == NULL) return;
852  if (oldCurrentLogEntryCopy->data.buffer != NULL)
853  delete[](char *)(oldCurrentLogEntryCopy->data.buffer);
854  delete oldCurrentLogEntryCopy;
855 }
856 
858 {
859  if (d_preload) {
860  // everything is already in memory, so just remember where we were
863  }
864  else if (d_accumulate) // but not pre-load
865  {
866  // our current location will remain in memory
868  d_bookmark.file_pos = ftell(d_file);
870  }
871  else // !preload and !accumulate
872  {
874  d_bookmark.file_pos = ftell(d_file);
875  if (d_currentLogEntry == NULL) // at the end of the file
876  {
877  if (d_bookmark.oldCurrentLogEntryCopy != NULL) {
879  delete[](
882  }
884  }
885  else {
886  if (d_bookmark.oldCurrentLogEntryCopy == NULL) {
888  if (d_bookmark.oldCurrentLogEntryCopy == NULL) {
889  fprintf(stderr, "Out of memory error: "
890  "vrpn_File_Connection::store_stream_"
891  "bookmark\n");
892  d_bookmark.valid = false;
893  return false;
894  }
898  }
910  delete[](char *)d_bookmark.oldCurrentLogEntryCopy->data.buffer;
911  }
915  d_bookmark.valid = false;
916  return false;
917  }
921  }
922  }
923  d_bookmark.valid = true;
924  return true;
925 }
926 
928 {
929  int retval = 0;
930  if (!d_bookmark.valid) return false;
931  if (d_preload) {
934  }
935  else if (d_accumulate) // but not pre-load
936  {
939  retval |= fseek(d_file, d_bookmark.file_pos, SEEK_SET);
940  }
941  else // !preload and !accumulate
942  {
943  if (d_bookmark.oldCurrentLogEntryCopy == NULL) {
944  // we were at the end of the file.
947  retval |= fseek(d_file, d_bookmark.file_pos, SEEK_SET);
948  }
949  else {
950  char *newBuffer =
952  if (newBuffer == NULL) { // make sure we can allocate the memory
953  // before we do anything else
954  return false;
955  }
957  retval |= fseek(d_file, d_bookmark.file_pos, SEEK_SET);
958  if (d_currentLogEntry == NULL) // we are at the end of the file
959  {
962  }
973  char *temp = (char *)d_currentLogEntry->data.buffer;
974  d_currentLogEntry->data.buffer = newBuffer;
975  memcpy((char *)d_currentLogEntry->data.buffer,
978  if (temp) delete[] temp;
980  }
981  }
982  return (retval == 0);
983 }
984 
986 
987 // Returns the time since the connection opened.
988 // Some subclasses may redefine time.
989 // virtual
991 {
994  }
996  *elapsed_time = vrpn_TimevalDiff(d_time, d_earliest_user_time);
997  }
998  else {
999  *elapsed_time = vrpn_TimevalDiff(d_time, d_start_time);
1000  } // XXX get rid of this option - dtm
1001 
1002  return 0;
1003 }
1004 
1005 // virtual
1007 {
1008  return this;
1009 }
1010 
1011 // {{{ read_cookie and read_entry
1012 
1013 // Reads a cookie from the logfile and calls check_vrpn_cookie()
1014 // (from vrpn_Connection.C) to check it.
1015 // Returns -1 on no cookie or cookie mismatch (which should cause abort),
1016 // 0 otherwise.
1017 
1018 // virtual
1020 {
1021  char readbuf[2048]; // HACK!
1022  size_t bytes = fread(readbuf, vrpn_cookie_size(), 1, d_file);
1023  if (bytes == 0) {
1024  fprintf(stderr, "vrpn_File_Connection::read_cookie: "
1025  "No cookie. If you're sure this is a logfile, "
1026  "run add_vrpn_cookie on it and try again.\n");
1027  return -1;
1028  }
1029 
1030  int retval = check_vrpn_file_cookie(readbuf);
1031  if (retval < 0) {
1032  return -1;
1033  }
1034 
1035  // TCH July 2001
1036  if (!d_endpoints[0]) {
1037  fprintf(stderr, "vrpn_File_Connection::read_cookie: "
1038  "No endpoints[0]. Internal failure.\n");
1039  return -1;
1040  }
1041  d_endpoints[0]->d_inLog->setCookie(readbuf);
1042 
1043  return 0;
1044 }
1045 
1046 // virtual
1048 {
1049  vrpn_LOGLIST *newEntry;
1050  size_t retval;
1051 
1052  newEntry = new vrpn_LOGLIST;
1053  if (!newEntry) {
1054  fprintf(stderr, "vrpn_File_Connection::read_entry: Out of memory.\n");
1055  return -1;
1056  }
1057 
1058  // Only print this message every second or so
1059  if (!d_file) {
1060  struct timeval now;
1061  vrpn_gettimeofday(&now, NULL);
1062  if (now.tv_sec != d_last_told.tv_sec) {
1063  fprintf(stderr, "vrpn_File_Connection::read_entry: no open file\n");
1064  memcpy(&d_last_told, &now, sizeof(d_last_told));
1065  }
1066  delete newEntry;
1067  return -1;
1068  }
1069 
1070  // Get the header of the next message. This was done as a horrible
1071  // hack in the past, where we read the sizeof a struct from the file,
1072  // including a pointer. This of course changed on 64-bit architectures.
1073  // The pointer value was not needed. We now read it as an array of
1074  // 32-bit values and then stuff these into the structure. Unfortunately,
1075  // we now need to both send and read the bogus pointer value if we want
1076  // to be compatible with old versions of log files.
1077 
1078  vrpn_HANDLERPARAM &header = newEntry->data;
1079  vrpn_int32 values[6];
1080  retval = fread(values, sizeof(vrpn_int32), 6, d_file);
1081 
1082  // return 1 if nothing to read OR end-of-file;
1083  // the latter isn't an error state
1084  if (retval <= 0) {
1085  // Don't close the file because we might get a reset message...
1086  delete newEntry;
1087  return 1;
1088  }
1089 
1090  header.type = ntohl(values[0]);
1091  header.sender = ntohl(values[1]);
1092  header.msg_time.tv_sec = ntohl(values[2]);
1093  header.msg_time.tv_usec = ntohl(values[3]);
1094  header.payload_len = ntohl(values[4]);
1095  header.buffer =
1096  NULL; // values[5] is ignored -- it used to hold the bogus pointer.
1097 
1098  // get the body of the next message
1099 
1100  if (header.payload_len > 0) {
1101  header.buffer = new char[header.payload_len];
1102  if (!header.buffer) {
1103  fprintf(stderr, "vrpn_File_Connection::read_entry: "
1104  "Out of memory.\n");
1105  return -1;
1106  }
1107 
1108  retval = fread((char *)header.buffer, 1, header.payload_len, d_file);
1109  }
1110 
1111  // return 1 if nothing to read OR end-of-file;
1112  // the latter isn't an error state
1113  if (retval <= 0) {
1114  // Don't close the file because we might get a reset message...
1115  return 1;
1116  }
1117 
1118  // If we are accumulating messages, keep the list of them up to
1119  // date. If we are not, toss the old to make way for the new.
1120  // Whenever this function returns 0, we need to have set the
1121  // Head and Tail to something non-NULL.
1122 
1123  if (d_accumulate) {
1124 
1125  // doubly-linked list maintenance, putting this one at the tail.
1126  newEntry->next = NULL;
1127  newEntry->prev = d_logTail;
1128  if (d_logTail) {
1129  d_logTail->next = newEntry;
1130  }
1131  d_logTail = newEntry;
1132 
1133  // If we've not gotten any messages yet, this one is also the
1134  // head.
1135  if (!d_logHead) {
1136  d_logHead = d_logTail;
1137  }
1138  }
1139  else { // Don't keep old list entries.
1140 
1141  // If we had a message before, get rid of it and its data now. We
1142  // could use either Head or Tail here because they will point
1143  // to the same message.
1144  if (d_logTail) {
1145  if (d_logTail->data.buffer) {
1146  delete[](char *)d_logTail -> data.buffer;
1147  }
1148  delete d_logTail;
1149  }
1150 
1151  // This is the only message in memory, so it is both the
1152  // head and the tail of the memory list.
1153  d_logHead = d_logTail = newEntry;
1154 
1155  // The new entry is not linked to any others (there are no others)
1156  newEntry->next = NULL;
1157  newEntry->prev = NULL;
1158  }
1159 
1160  return 0;
1161 }
1162 // }}}
1163 
1164 // virtual
1166 {
1167  if (d_file) {
1168  fclose(d_file);
1169  }
1170  d_file = NULL;
1171  return 0;
1172 }
1173 
1175 {
1176  // make it as if we never saw any messages from our previous activity
1178 
1179  // If we are accumulating, reset us back to the beginning of the memory
1180  // buffer chain. Otherwise, go back to the beginning of the file and
1181  // then read the magic cookie and then the first entry again.
1182  if (d_accumulate) {
1184  }
1185  else {
1186  rewind(d_file);
1187  read_cookie();
1188  read_entry();
1190  }
1192  // reset for mainloop()
1193  d_last_time.tv_usec = d_last_time.tv_sec = 0;
1195 
1196  // This is useful to play the initial system messages
1197  // (the sender/type ones) automatically. These might not be
1198  // time synched so if we don't play them automatically they
1199  // can mess up playback if their timestamps are later then
1200  // the first user message.
1203  }
1204 
1205  return 0;
1206 }
1207 
1208 // static
1211 {
1212  vrpn_File_Connection *me = (vrpn_File_Connection *)userdata;
1213 
1214  const char *bufPtr = p.buffer;
1215  me->set_replay_rate(vrpn_unbuffer<vrpn_float32>(bufPtr));
1216 
1217  return 0;
1218 }
1219 
1220 // static
1222 {
1223  vrpn_File_Connection *me = (vrpn_File_Connection *)userdata;
1224 
1225  // fprintf(stderr, "In vrpn_File_Connection::handle_reset().\n");
1226 
1227  return me->reset();
1228 }
1229 
1230 // static
1233 {
1234  vrpn_File_Connection *me = (vrpn_File_Connection *)userdata;
1235  timeval newtime;
1236 
1237  newtime.tv_sec = ((vrpn_int32 *)(p.buffer))[0];
1238  newtime.tv_usec = ((vrpn_int32 *)(p.buffer))[1];
1239 
1240  return me->play_to_time(newtime);
1241 }
1242 
1244 {
1245  // Do nothing except clear the buffer -
1246  // file connections aren't really connected to anything.
1247 
1248  d_endpoints[0]->clearBuffers(); // Clear the buffer for the next time
1249  return 0;
1250 }
1251 
1252 // }}}
struct timeval msg_time
vrpn_uint32 get_Jane_value(void)
int jump_to_filetime(timeval absolute_time)
virtual int mainloop(const timeval *timeout=NULL)
virtual int time_since_connection_open(timeval *elapsed_time)
Returns the time since the connection opened. Some subclasses may redefine time.
vrpn_LOGLIST * prev
vrpn_int32 payload_len
virtual ~vrpn_File_Connection(void)
void set_replay_rate(vrpn_float32 rate)
size_t vrpn_cookie_size(void)
Returns the size of the magic cookie buffer, plus any alignment overhead.
bool vrpn_FILE_CONNECTIONS_SHOULD_SKIP_TO_USER_MESSAGES
int logIncomingMessage(size_t payloadLen, struct timeval time, vrpn_int32 type, vrpn_int32 sender, const char *buffer)
Should be called with the timeval adjusted by the clock offset on the receiving Endpoint.
const vrpn_int32 vrpn_CONNECTION_UDP_DESCRIPTION
virtual int send_pending_reports(void)
send pending report, clear the buffer. This function was protected, now is public,...
static int VRPN_CALLBACK handle_set_replay_rate(void *, vrpn_HANDLERPARAM)
const char * buffer
int play_to_time(vrpn_float64 end_time)
timeval vrpn_TimevalDiff(const timeval &tv1, const timeval &tv2)
Definition: vrpn_Shared.C:92
void drop_connection(void)
Should only be called by vrpn_Connection::drop_connection(), since there's more housecleaning to do a...
void accumulate_to(const timeval &now_time)
char * vrpn_copy_file_name(const char *filespecifier)
Utility routines to parse file specifiers FROM service locations.
static int VRPN_CALLBACK handle_reset(void *, vrpn_HANDLERPARAM)
Generic connection class not specific to the transport mechanism.
bool vrpn_FILE_CONNECTIONS_SHOULD_PRELOAD
int play_to_filetime(const timeval end_filetime)
virtual int register_handler(vrpn_int32 type, vrpn_MESSAGEHANDLER handler, void *userdata, vrpn_int32 sender=vrpn_ANY_SENDER)
Set up (or remove) a handler for a message of a given type. Optionally, specify which sender to handl...
bool vrpn_FILE_CONNECTIONS_SHOULD_ACCUMULATE
FileTime_Accumulator d_filetime_accum
vrpn_FileBookmark d_bookmark
int check_vrpn_file_cookie(const char *buffer)
virtual int read_cookie(void)
timeval vrpn_TimevalScale(const timeval &tv, double scale)
Definition: vrpn_Shared.C:102
void reset_at_time(const timeval &now_time)
void addConnection(vrpn_Connection *, const char *name)
NB implementation is not particularly efficient; we expect to have O(10) connections,...
virtual int read_entry(void)
int setCookie(const char *cookieBuffer)
The magic cookie is set to the default value of the version of VRPN compiled, but a more correct valu...
void deleteConnection(vrpn_Connection *)
double vrpn_TimevalMsecs(const timeval &tv)
Definition: vrpn_Shared.C:141
timeval vrpn_MsecsTimeval(const double dMsecs)
Definition: vrpn_Shared.C:146
This structure is what is passed to a vrpn_Connection message callback.
vrpn_File_Connection(const char *station_name, const char *local_in_logfile_name=NULL, const char *local_out_logfile_name=NULL)
int connectionStatus
Status of the connection.
static vrpn_ConnectionManager & instance(void)
The only way to get access to an instance of this class. Guarantees that there is only one,...
virtual int advance_currentLogEntry(void)
bool vrpn_TimevalGreater(const timeval &tv1, const timeval &tv2)
Definition: vrpn_Shared.C:113
Encapsulation of the data and methods for a single generic connection to take care of one part of man...
int local_type_id(vrpn_int32 remote_type) const
Returns the local mapping for the remote type (-1 if none).
int doSystemCallbacksFor(vrpn_HANDLERPARAM, void *)
int need_to_play(timeval filetime)
vrpn_Log * d_inLog
vrpn_HANDLERPARAM data
#define vrpn_gettimeofday
Definition: vrpn_Shared.h:89
vrpn_Endpoint_IP * d_endpoints[vrpn_MAX_ENDPOINTS]
Sockets used to talk to remote Connection(s) and other information needed on a per-connection basis.
static int VRPN_CALLBACK handle_play_to_time(void *, vrpn_HANDLERPARAM)
virtual int close_file(void)
int jump_to_time(vrpn_float64 newtime)
void clearBuffers(void)
Empties out the TCP and UDP send buffers. Needed by vrpn_FileConnection to get at {udp,...
int playone_to_filetime(timeval end_filetime)
vrpn_LOGLIST * d_currentLogEntry
timeval vrpn_TimevalSum(const timeval &tv1, const timeval &tv2)
Definition: vrpn_Shared.C:45
virtual vrpn_File_Connection * get_File_Connection(void)
vrpn_File_Connection implements this as "return this" so it can be used to detect a File_Connection a...
vrpn_LOGLIST * next
int local_sender_id(vrpn_int32 remote_sender) const
Returns the local mapping for the remote sender (-1 if none).
Placed here so vrpn_FileConnection can use it too.
virtual int do_callbacks_for(vrpn_int32 type, vrpn_int32 sender, struct timeval time, vrpn_uint32 len, const char *buffer)