Timeline: Fix graphics corruption. Also, indicate loop point by modifying waveform...
[nondaw.git] / nonlib / Loggable.C
blob89e8d7943f28a2af495eed94a040d2d41278848b
2 /*******************************************************************************/
3 /* Copyright (C) 2008 Jonathan Moore Liles                                     */
4 /*                                                                             */
5 /* This program is free software; you can redistribute it and/or modify it     */
6 /* under the terms of the GNU General Public License as published by the       */
7 /* Free Software Foundation; either version 2 of the License, or (at your      */
8 /* option) any later version.                                                  */
9 /*                                                                             */
10 /* This program is distributed in the hope that it will be useful, but WITHOUT */
11 /* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       */
12 /* FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for   */
13 /* more details.                                                               */
14 /*                                                                             */
15 /* You should have received a copy of the GNU General Public License along     */
16 /* with This program; see the file COPYING.  If not,write to the Free Software */
17 /* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
18 /*******************************************************************************/
20 /* This class handles all journaling. All journaled objects must
21    inherit from Loggable as well as define a few special methods (via
22    macros), get and set methods, and have contructors and destructors
23    that call log_create() and log_destroy() in the appropriate
24    order. Any action that might affect multiple loggable objects
25    *must* be braced by calls to Loggable::block_start() and
26    Loggable::block_end() in order for Undo to work properly. */
28 #include "Loggable.H"
30 #include <stdlib.h>
31 #include <stdio.h>
32 #include <string.h>
34 #include "file.h"
36 // #include "const.h"
37 #include "debug.h"
39 #include <algorithm>
40 using std::min;
41 using std::max;
45 FILE *Loggable::_fp;
46 unsigned int Loggable::_log_id = 0;
47 int Loggable::_level = 0;
48 int Loggable::_dirty = 0;
49 off_t Loggable::_undo_offset = 0;
51 std::map <unsigned int, Loggable::log_pair > Loggable::_loggables;
53 std::map <std::string, create_func*> Loggable::_class_map;
54 std::queue <char *> Loggable::_transaction;
56 progress_func *Loggable::_progress_callback = NULL;
57 void *Loggable::_progress_callback_arg = NULL;
59 snapshot_func *Loggable::_snapshot_callback = NULL;
60 void *Loggable::_snapshot_callback_arg = NULL;
62 dirty_func *Loggable::_dirty_callback = NULL;
63 void *Loggable::_dirty_callback_arg = NULL;
67 Loggable::~Loggable ( )
69     _loggables[ _id ].loggable = NULL;
74 void
75 Loggable::block_start ( void )
77     ++Loggable::_level;
80 void
81 Loggable::block_end ( void )
83     --Loggable::_level;
85     ASSERT( Loggable::_level >= 0, "Programming error" );
87     if ( Loggable::_level == 0 )
88         flush();
91 Loggable *
92 Loggable::find ( unsigned int id )
94     if ( _relative_id )
95         id += _relative_id;
97     return _loggables[ id ].loggable;
100 /** Open the journal /filename/ and replay it, bringing the end state back into RAM */
101 bool
102 Loggable::open ( const char *filename )
104     FILE *fp;
106     Loggable::_fp = NULL;
108     if ( ! ( fp = fopen( filename, "a+" ) ) )
109     {
110         WARNING( "Could not open log file for writing!" );
111         return false;
112     }
114     load_unjournaled_state();
116     if ( newer( "snapshot", filename ) )
117     {
118         MESSAGE( "Loading snapshot" );
120         FILE *fp = fopen( "snapshot", "r" );
122         replay( fp );
124         fclose( fp );
125     }
126     else
127     {
128         MESSAGE( "Replaying journal" );
130         replay( fp );
131     }
133     fseek( fp, 0, SEEK_END );
134     _undo_offset = ftell( fp );
136     Loggable::_fp = fp;
138     return true;
141 bool
142 Loggable::load_unjournaled_state ( void )
144     FILE *fp;
146     fp = fopen( "unjournaled", "r" );
148     if ( ! fp )
149     {
150         DWARNING( "Could not open unjournaled state file for reading" );
151         return false;
152     }
154     unsigned int id;
155     char buf[BUFSIZ];
157     while ( fscanf( fp, "%X set %[^\n]\n", &id, buf ) == 2 )
158         _loggables[ id ].unjournaled_state = new Log_Entry( buf );
160     fclose( fp );
162     return true;
165 #include <sys/stat.h>
166 #include <unistd.h>
168 /** replay journal or snapshot */
169 bool
170 Loggable::replay ( const char *file )
172     if ( FILE *fp = fopen( file, "r" ) )
173     {
174         bool r = replay( fp );
176         fclose( fp );
178         return r;
179     }
180     else
181         return false;
184 /** replay journal or snapshot */
185 bool
186 Loggable::replay ( FILE *fp )
188     /* FIXME: bogus */
189     char buf[BUFSIZ];
191     struct stat st;
192     fstat( fileno( fp ), &st );
194     off_t total = st.st_size;
195     off_t current = 0;
197     if ( _progress_callback )
198         _progress_callback( 0, _progress_callback_arg );
200     while ( fscanf( fp, "%[^\n]\n", buf ) == 1 )
201     {
202         if ( ! ( ! strcmp( buf, "{" ) || ! strcmp( buf, "}" ) ) )
203         {
204             if ( *buf == '\t' )
205                 do_this( buf + 1, false );
206             else
207                 do_this( buf, false );
208         }
210         current = ftell( fp );
212         if ( _progress_callback )
213             _progress_callback( current * 100 / total, _progress_callback_arg );
214     }
216     if ( _progress_callback )
217         _progress_callback( 0, _progress_callback_arg );
219     clear_dirty();
221     return true;
224 /** close journal and delete all loggable objects, returing the systemt to a blank slate */
225 bool
226 Loggable::close ( void )
228     DMESSAGE( "closing journal and destroying all journaled objects" );
230     if ( _fp )
231     {
232         fclose( _fp );
233         _fp = NULL;
234     }
236     if ( ! snapshot( "snapshot" ) )
237         WARNING( "Failed to create snapshot" );
239     for ( std::map <unsigned int, Loggable::log_pair >::iterator i = _loggables.begin();
240           i != _loggables.end(); ++i )
241         if ( i->second.loggable )
242             delete i->second.loggable;
244     save_unjournaled_state();
246     for ( std::map <unsigned int, Loggable::log_pair >::iterator i = _loggables.begin();
247           i != _loggables.end(); ++i )
248         if ( i->second.unjournaled_state )
249             delete i->second.unjournaled_state;
251     _loggables.clear();
253     return true;
257 /** save out unjournaled state for all loggables */
258 bool
259 Loggable::save_unjournaled_state ( void )
261     FILE *fp;
263     fp = fopen( "unjournaled", "w" );
265     if ( ! fp )
266     {
267         DWARNING( "Could not open unjournaled state file for writing!" );
268         return false;
269     }
271     for ( std::map <unsigned int, Loggable::log_pair >::iterator i = _loggables.begin();
272           i != _loggables.end(); ++i )
273     {
274         if ( i->second.unjournaled_state )
275         {
276             char *s = i->second.unjournaled_state->print();
278             fprintf( fp, "0x%X set %s\n", i->first, s );
280             free( s );
281         }
282     }
284     fclose( fp );
286     return true;
289 /** must be called after construction in create() methods */
290 void
291 Loggable::update_id ( unsigned int id )
293     /* make sure we're the last one */
294     ASSERT( _id == _log_id, "%u != %u", _id, _log_id );
295     assert( _loggables[ _id ].loggable == this );
297     _loggables[ _id ].loggable = NULL;
299     _log_id = max( _log_id, id );
301     /* return this id number to the system */
302 //            --_log_id;
304     _id = id;
306     if ( _loggables[ _id ].loggable )
307         FATAL( "Attempt to create object with an ID (0x%X) that already exists. The existing object is of type \"%s\", the new one is \"%s\". Corrupt journal?", _id, _loggables[ _id ].loggable->class_name(), class_name() );
309     _loggables[ _id ].loggable = this;
312 /** return a pointer to a static copy of /s/ with all special characters escaped */
313 const char *
314 Loggable::escape ( const char *s )
316     static char r[512];
318     size_t i = 0;
319     for ( ; *s && i < sizeof( r ); ++i, ++s )
320     {
321         if ( '\n' == *s )
322         {
323             r[ i++ ] = '\\';
324             r[ i ] = 'n';
325         }
326         else if ( '"' == *s )
327         {
328             r[ i++ ] = '\\';
329             r[ i ] = '"';
330         }
331         else
332             r[ i ] = *s;
333     }
335     r[ i ] = '\0';
337     return r;
340 unsigned int Loggable::_relative_id = 0;
342 /* calls to do_this() between invocation of this method and
343  * end_relative_id_mode() will have all their IDs made relative to the
344  * highest available ID at this time of this call. Non-Mixer uses
345  * this to allow importing of module chains */
346 void
347 Loggable::begin_relative_id_mode ( void )
349     _relative_id = ++_log_id;
352 void
353 Loggable::end_relative_id_mode ( void )
355     _relative_id = 0;
359 /** 'do' a message like "Audio_Region 0xF1 set :r 123" */
360 bool
361 Loggable::do_this ( const char *s, bool reverse )
363     unsigned int id = 0;
365     char classname[40];
366     char command[40];
367     char *arguments = NULL;
369     int found = sscanf( s, "%s %X %s ", classname, &id, command );
371     ASSERT( 3 == found,  "Invalid journal entry format \"%s\"", s );
373     const char *create, *destroy;
375     if ( reverse )
376     {
377 //        sscanf( s, "%s %*X %s %*[^\n<]<< %a[^\n]", classname, command, &arguments );
378         sscanf( s, "%s %*X %s%*[^\n<]<< %a[^\n]", classname, command, &arguments );
379         create = "destroy";
380         destroy = "create";
382         DMESSAGE( "undoing \"%s\"", s );
383     }
384     else
385     {
386         sscanf( s, "%s %*X %s %a[^\n<]", classname, command, &arguments );
387         create = "create";
388         destroy = "destroy";
389     }
391     if ( ! strcmp( command, destroy ) )
392     {
393         Loggable *l = find( id );
395         /* deleting eg. a track, which contains a list of other
396            widgets, causes destroy messages to be emitted for all those
397            widgets, but when replaying the journal the destroy message
398            causes the children to be deleted also... This is a temporary
399            hack. Would it be better to queue up objects for deletion
400            (when?) */
401         if ( l )
402             delete l;
403     }
404     else if ( ! strcmp( command, "set" ) )
405     {
406 //        printf( "got set command (%s).\n", arguments );
408         Loggable *l = find( id );
410         ASSERT( l, "Unable to find object 0x%X referenced by command \"%s\"", id, s );
412         Log_Entry e( arguments );
414         l->log_start();
415         l->set( e );
416         l->log_end();
417     }
418     else if ( ! strcmp( command, create ) )
419     {
420         Log_Entry e( arguments );
422         ASSERT( _class_map[ std::string( classname ) ], "Journal contains an object of class \"%s\", but I don't know how to create such objects.", classname );
424         {
425             if ( _relative_id )
426                 id += _relative_id;
428             /* create */
429             Loggable *l = _class_map[ std::string( classname ) ]( e, id );
430             l->log_create();
432             /* we're now creating a loggable. Apply any unjournaled
433              * state it may have had in the past under this log ID */
435             Log_Entry *e = _loggables[ id ].unjournaled_state;
437             if ( e )
438                 l->set( *e );
439         }
441     }
443     if ( arguments )
444         free( arguments );
446     return true;
449 /** Reverse the last journal transaction */
450 void
451 Loggable::undo ( void )
453     const int bufsiz = 1024;
454     char buf[bufsiz];
456     block_start();
458     long here = ftell( _fp );
460     fseek( _fp, _undo_offset, SEEK_SET );
462     backwards_fgets( buf, bufsiz, _fp );
464     if ( ! strcmp( buf, "}\n" ) )
465     {
466         DMESSAGE( "undoing block" );
467         for ( ;; )
468         {
469             backwards_fgets( buf, bufsiz, _fp );
471             char *s = buf;
472             if ( *s != '\t' )
473                 break;
474             else
475                 ++s;
477             do_this( s, true );
478         }
479     }
480     else
481         do_this( buf, true );
483     off_t uo = ftell( _fp );
485     ASSERT( _undo_offset <= here, "WTF?" );
487     block_end();
489     _undo_offset = uo;
492 /** write a snapshot of the current state of all loggable objects to
493  * file handle /fp/ */
494 bool
495 Loggable::snapshot ( FILE *fp )
497     FILE *ofp = _fp;
499     if ( ! Loggable::_snapshot_callback )
500     {
501         DWARNING( "No snapshot callback defined" );
502         return false;
503     }
505     if ( ! ( _fp = fp ) )
506     {
507         _fp = ofp;
508         return false;
509     }
511     block_start();
513     Loggable::_snapshot_callback( _snapshot_callback_arg );
515     block_end();
517     _fp = ofp;
519     clear_dirty();
521     return true;
524 /** write a snapshot of the current state of all loggable objects to
525  * file /name/ */
526 bool
527 Loggable::snapshot ( const char *name )
529     FILE *fp;
531     if ( ! ( fp = fopen( name, "w" ) ))
532         return false;
534     bool r = snapshot( fp );
536     fclose( fp );
538     return r;
541 /** Replace the journal with a snapshot of the current state */
542 void
543 Loggable::compact ( void )
545     fseek( _fp, 0, SEEK_SET );
546     ftruncate( fileno( _fp ), 0 );
548     if ( ! snapshot( _fp ) )
549         FATAL( "Could not write snapshot!" );
551     fseek( _fp, 0, SEEK_END );
554 #include <stdarg.h>
556 /** Writes (part of) a line to the journal. Each separate line will be
557  * stored separately in _transaction until transaction is closed.
558  */
559 void
560 Loggable::log ( const char *fmt, ... )
562     static char * buf = NULL;
563     static size_t i = 0;
564     static size_t buf_size = 0;
566     if ( ! _fp )
567         return;
569     if ( NULL == buf )
570     {
571         buf_size = 1024;
572         buf = (char*)malloc( buf_size );
573     }
575     va_list args;
577     if ( fmt )
578     {
579         va_start( args, fmt );
581         for ( ;; )
582         {
583             size_t l = vsnprintf( buf + i, buf_size - i, fmt, args );
585             if ( l >= buf_size - i )
586             {
587                 buf = (char*)realloc( buf, buf_size += (l + 1) + buf_size );
588             }
589             else
590             {
591                 i += l;
592                 break;
593             }
594         }
596         va_end( args );
597     }
599     if ( '\n' == buf[i-1] )
600     {
601         _transaction.push( strdup( buf ) );
602         i = 0;
603     }
606 /** End the current transaction and commit it to the journal */
607 void
608 Loggable::flush ( void )
610     if ( ! _fp )
611     {
612 //        printf( "error: no log file open!\n" );
614         while ( ! _transaction.empty() )
615         {
616             free( _transaction.front() );
617             _transaction.pop();
618         }
620         return;
621     }
623     int n = _transaction.size();
625     if ( n > 1 )
626         fprintf( _fp, "{\n" );
628     while ( ! _transaction.empty() )
629     {
630         char *s = _transaction.front();
632         _transaction.pop();
634         if ( n > 1 )
635             fprintf( _fp, "\t" );
637         fprintf( _fp, "%s", s );
639         free( s );
640     }
642     if ( n > 1 )
643         fprintf( _fp, "}\n" );
645     if ( n )
646         /* something done, reset undo index */
647         _undo_offset = ftell( _fp );
649     fflush( _fp );
652 /** Print bidirectional journal entry */
653 void
654 Loggable::log_print( const Log_Entry *o, const Log_Entry *n ) const
656     if ( ! _fp )
657         return;
659     if ( n )
660         for ( int i = 0; i < n->size(); ++i )
661         {
662             const char *s, *v;
664             n->get( i, &s, &v );
666             log( "%s %s%s", s, v, n->size() == i + 1 ? "" : " " );
667         }
669     if ( o && o->size() )
670     {
671         if ( n ) log( " << " );
673         for ( int i = 0; i < o->size(); ++i )
674         {
675             const char *s, *v;
677             o->get( i, &s, &v );
679             log( "%s %s%s", s, v, o->size() == i + 1 ? "" : " " );
680         }
681     }
683     log( "\n" );
686 /** Remember current object state for later comparison. *Must* be
687  * called before any user action that might change one of the object's
688  * journaled properties.  */
689 void
690 Loggable::log_start ( void )
692     if ( ! _old_state )
693     {
694         _old_state = new Log_Entry;
696         get( *_old_state );
697     }
698     ++_nest;
701 /** Log any change to the object's state since log_start(). */
702 void
703 Loggable::log_end ( void )
705     ASSERT( _old_state, "Programming error: log_end() called before log_start()" );
707     if ( --_nest > 0 )
708         return;
710     Log_Entry *new_state;
712     new_state = new Log_Entry;
714     get( *new_state );
716     if ( Log_Entry::diff( _old_state, new_state ) )
717     {
718         log( "%s 0x%X set ", class_name(), _id );
720         log_print( _old_state, new_state );
722         set_dirty();
723     }
725     delete new_state;
726     delete _old_state;
728     _old_state = NULL;
730     if ( Loggable::_level == 0 )
731         Loggable::flush();
734 /** Log object creation. *Must* be called at the end of all public
735  * constructors for leaf classes  */
737 void
738 Loggable::log_create ( void ) const
740     set_dirty();
742     if ( ! _fp )
743         /* replaying, don't bother */
744         return;
746     log( "%s 0x%X create ", class_name(), _id );
748     Log_Entry e;
750     get( e );
752     if ( e.size() )
753         log_print( NULL, &e );
754     else
755         log( "\n" );
757     if ( Loggable::_level == 0 )
758         Loggable::flush();
761 /** record this loggable's unjournaled state in memory */
762 void
763 Loggable::record_unjournaled ( void ) const
765     Log_Entry *e = new Log_Entry();
767     get_unjournaled( *e );
769     Log_Entry *le = _loggables[ _id ].unjournaled_state;
771     if ( le )
772         delete le;
774     if ( e->size() )
775         _loggables[ _id ].unjournaled_state = e;
776     else
777     {
778         /* don't waste space on loggables with no unjournaled properties */
779         _loggables[ _id ].unjournaled_state = NULL;
780         delete e;
781     }
784 /** Log object destruction. *Must* be called at the beginning of the
785  * destructors of leaf classes */
786 void
787 Loggable::log_destroy ( void ) const
789     /* the unjournaled state may have changed: make a note of it. */
790     record_unjournaled();
792     set_dirty();
794     if ( ! _fp )
795         /* tearing down... don't bother */
796         return;
798     log( "%s 0x%X destroy << ", class_name(), _id );
800     Log_Entry e;
802     get( e );
804     log_print( NULL, &e );
806     if ( Loggable::_level == 0 )
807         Loggable::flush();