2 /*******************************************************************************/
3 /* Copyright (C) 2008 Jonathan Moore Liles */
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. */
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 */
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. */
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;
75 Loggable::block_start ( void )
81 Loggable::block_end ( void )
85 ASSERT( Loggable::_level >= 0, "Programming error" );
87 if ( Loggable::_level == 0 )
92 Loggable::find ( unsigned int id )
97 return _loggables[ id ].loggable;
100 /** Open the journal /filename/ and replay it, bringing the end state back into RAM */
102 Loggable::open ( const char *filename )
106 Loggable::_fp = NULL;
108 if ( ! ( fp = fopen( filename, "a+" ) ) )
110 WARNING( "Could not open log file for writing!" );
114 load_unjournaled_state();
116 if ( newer( "snapshot", filename ) )
118 MESSAGE( "Loading snapshot" );
120 FILE *fp = fopen( "snapshot", "r" );
128 MESSAGE( "Replaying journal" );
133 fseek( fp, 0, SEEK_END );
134 _undo_offset = ftell( fp );
142 Loggable::load_unjournaled_state ( void )
146 fp = fopen( "unjournaled", "r" );
150 DWARNING( "Could not open unjournaled state file for reading" );
157 while ( fscanf( fp, "%X set %[^\n]\n", &id, buf ) == 2 )
158 _loggables[ id ].unjournaled_state = new Log_Entry( buf );
165 #include <sys/stat.h>
168 /** replay journal or snapshot */
170 Loggable::replay ( const char *file )
172 if ( FILE *fp = fopen( file, "r" ) )
174 bool r = replay( fp );
184 /** replay journal or snapshot */
186 Loggable::replay ( FILE *fp )
192 fstat( fileno( fp ), &st );
194 off_t total = st.st_size;
197 if ( _progress_callback )
198 _progress_callback( 0, _progress_callback_arg );
200 while ( fscanf( fp, "%[^\n]\n", buf ) == 1 )
202 if ( ! ( ! strcmp( buf, "{" ) || ! strcmp( buf, "}" ) ) )
205 do_this( buf + 1, false );
207 do_this( buf, false );
210 current = ftell( fp );
212 if ( _progress_callback )
213 _progress_callback( current * 100 / total, _progress_callback_arg );
216 if ( _progress_callback )
217 _progress_callback( 0, _progress_callback_arg );
224 /** close journal and delete all loggable objects, returing the systemt to a blank slate */
226 Loggable::close ( void )
228 DMESSAGE( "closing journal and destroying all journaled objects" );
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;
257 /** save out unjournaled state for all loggables */
259 Loggable::save_unjournaled_state ( void )
263 fp = fopen( "unjournaled", "w" );
267 DWARNING( "Could not open unjournaled state file for writing!" );
271 for ( std::map <unsigned int, Loggable::log_pair >::iterator i = _loggables.begin();
272 i != _loggables.end(); ++i )
274 if ( i->second.unjournaled_state )
276 char *s = i->second.unjournaled_state->print();
278 fprintf( fp, "0x%X set %s\n", i->first, s );
289 /** must be called after construction in create() methods */
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 */
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 */
314 Loggable::escape ( const char *s )
319 for ( ; *s && i < sizeof( r ); ++i, ++s )
326 else if ( '"' == *s )
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 */
347 Loggable::begin_relative_id_mode ( void )
349 _relative_id = ++_log_id;
353 Loggable::end_relative_id_mode ( void )
359 /** 'do' a message like "Audio_Region 0xF1 set :r 123" */
361 Loggable::do_this ( const char *s, bool reverse )
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;
377 // sscanf( s, "%s %*X %s %*[^\n<]<< %a[^\n]", classname, command, &arguments );
378 sscanf( s, "%s %*X %s%*[^\n<]<< %a[^\n]", classname, command, &arguments );
382 DMESSAGE( "undoing \"%s\"", s );
386 sscanf( s, "%s %*X %s %a[^\n<]", classname, command, &arguments );
391 if ( ! strcmp( command, destroy ) )
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
404 else if ( ! strcmp( command, "set" ) )
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 );
418 else if ( ! strcmp( command, create ) )
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 );
429 Loggable *l = _class_map[ std::string( classname ) ]( e, id );
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;
449 /** Reverse the last journal transaction */
451 Loggable::undo ( void )
453 const int bufsiz = 1024;
458 long here = ftell( _fp );
460 fseek( _fp, _undo_offset, SEEK_SET );
462 backwards_fgets( buf, bufsiz, _fp );
464 if ( ! strcmp( buf, "}\n" ) )
466 DMESSAGE( "undoing block" );
469 backwards_fgets( buf, bufsiz, _fp );
481 do_this( buf, true );
483 off_t uo = ftell( _fp );
485 ASSERT( _undo_offset <= here, "WTF?" );
492 /** write a snapshot of the current state of all loggable objects to
493 * file handle /fp/ */
495 Loggable::snapshot ( FILE *fp )
499 if ( ! Loggable::_snapshot_callback )
501 DWARNING( "No snapshot callback defined" );
505 if ( ! ( _fp = fp ) )
513 Loggable::_snapshot_callback( _snapshot_callback_arg );
524 /** write a snapshot of the current state of all loggable objects to
527 Loggable::snapshot ( const char *name )
531 if ( ! ( fp = fopen( name, "w" ) ))
534 bool r = snapshot( fp );
541 /** Replace the journal with a snapshot of the current state */
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 );
556 /** Writes (part of) a line to the journal. Each separate line will be
557 * stored separately in _transaction until transaction is closed.
560 Loggable::log ( const char *fmt, ... )
562 static char * buf = NULL;
564 static size_t buf_size = 0;
572 buf = (char*)malloc( buf_size );
579 va_start( args, fmt );
583 size_t l = vsnprintf( buf + i, buf_size - i, fmt, args );
585 if ( l >= buf_size - i )
587 buf = (char*)realloc( buf, buf_size += (l + 1) + buf_size );
599 if ( '\n' == buf[i-1] )
601 _transaction.push( strdup( buf ) );
606 /** End the current transaction and commit it to the journal */
608 Loggable::flush ( void )
612 // printf( "error: no log file open!\n" );
614 while ( ! _transaction.empty() )
616 free( _transaction.front() );
623 int n = _transaction.size();
626 fprintf( _fp, "{\n" );
628 while ( ! _transaction.empty() )
630 char *s = _transaction.front();
635 fprintf( _fp, "\t" );
637 fprintf( _fp, "%s", s );
643 fprintf( _fp, "}\n" );
646 /* something done, reset undo index */
647 _undo_offset = ftell( _fp );
652 /** Print bidirectional journal entry */
654 Loggable::log_print( const Log_Entry *o, const Log_Entry *n ) const
660 for ( int i = 0; i < n->size(); ++i )
666 log( "%s %s%s", s, v, n->size() == i + 1 ? "" : " " );
669 if ( o && o->size() )
671 if ( n ) log( " << " );
673 for ( int i = 0; i < o->size(); ++i )
679 log( "%s %s%s", s, v, o->size() == i + 1 ? "" : " " );
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. */
690 Loggable::log_start ( void )
694 _old_state = new Log_Entry;
701 /** Log any change to the object's state since log_start(). */
703 Loggable::log_end ( void )
705 ASSERT( _old_state, "Programming error: log_end() called before log_start()" );
710 Log_Entry *new_state;
712 new_state = new Log_Entry;
716 if ( Log_Entry::diff( _old_state, new_state ) )
718 log( "%s 0x%X set ", class_name(), _id );
720 log_print( _old_state, new_state );
730 if ( Loggable::_level == 0 )
734 /** Log object creation. *Must* be called at the end of all public
735 * constructors for leaf classes */
738 Loggable::log_create ( void ) const
743 /* replaying, don't bother */
746 log( "%s 0x%X create ", class_name(), _id );
753 log_print( NULL, &e );
757 if ( Loggable::_level == 0 )
761 /** record this loggable's unjournaled state in memory */
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;
775 _loggables[ _id ].unjournaled_state = e;
778 /* don't waste space on loggables with no unjournaled properties */
779 _loggables[ _id ].unjournaled_state = NULL;
784 /** Log object destruction. *Must* be called at the beginning of the
785 * destructors of leaf classes */
787 Loggable::log_destroy ( void ) const
789 /* the unjournaled state may have changed: make a note of it. */
790 record_unjournaled();
795 /* tearing down... don't bother */
798 log( "%s 0x%X destroy << ", class_name(), _id );
804 log_print( NULL, &e );
806 if ( Loggable::_level == 0 )