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. */
34 #include "util/file.h"
37 #include "util/debug.h"
46 unsigned int Loggable::_log_id = 0;
47 int Loggable::_level = 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;
64 Loggable::~Loggable ( )
66 _loggables[ _id ].loggable = NULL;
72 Loggable::block_start ( void )
78 Loggable::block_end ( void )
82 ASSERT( Loggable::_level >= 0, "Programming error" );
84 if ( Loggable::_level == 0 )
89 Loggable::find ( unsigned int id )
91 return _loggables[ id ].loggable;
94 /** Open the journal /filename/ and replay it, bringing the end state back into RAM */
96 Loggable::open ( const char *filename )
100 Loggable::_fp = NULL;
102 if ( ! ( fp = fopen( filename, "a+" ) ) )
104 WARNING( "Could not open log file for writing!" );
108 load_unjournaled_state();
110 if ( newer( "snapshot", filename ) )
112 MESSAGE( "Loading snapshot" );
114 FILE *fp = fopen( "snapshot", "r" );
122 MESSAGE( "Replaying journal" );
127 fseek( fp, 0, SEEK_END );
128 _undo_offset = ftell( fp );
136 Loggable::load_unjournaled_state ( void )
140 fp = fopen( "unjournaled", "r" );
144 DWARNING( "Could not open unjournaled state file for reading" );
151 while ( fscanf( fp, "%X set %[^\n]\n", &id, buf ) == 2 )
152 _loggables[ id ].unjournaled_state = new Log_Entry( buf );
159 #include <sys/stat.h>
162 /** replay journal or snapshot */
164 Loggable::replay ( const char *file )
166 if ( FILE *fp = fopen( file, "r" ) )
168 bool r = replay( fp );
178 /** replay journal or snapshot */
180 Loggable::replay ( FILE *fp )
186 fstat( fileno( fp ), &st );
188 off_t total = st.st_size;
191 if ( _progress_callback )
192 _progress_callback( 0, _progress_callback_arg );
194 while ( fscanf( fp, "%[^\n]\n", buf ) == 1 )
196 if ( ! ( ! strcmp( buf, "{" ) || ! strcmp( buf, "}" ) ) )
199 do_this( buf + 1, false );
201 do_this( buf, false );
204 current = ftell( fp );
206 if ( _progress_callback )
207 _progress_callback( current * 100 / total, _progress_callback_arg );
210 if ( _progress_callback )
211 _progress_callback( 0, _progress_callback_arg );
216 /** close journal and delete all loggable objects, returing the systemt to a blank slate */
218 Loggable::close ( void )
220 DMESSAGE( "closing journal and destroying all journaled objects" );
228 if ( ! snapshot( "snapshot" ) )
229 WARNING( "Failed to create snapshot" );
231 for ( std::map <unsigned int, Loggable::log_pair >::iterator i = _loggables.begin();
232 i != _loggables.end(); ++i )
233 if ( i->second.loggable )
234 delete i->second.loggable;
236 save_unjournaled_state();
238 for ( std::map <unsigned int, Loggable::log_pair >::iterator i = _loggables.begin();
239 i != _loggables.end(); ++i )
240 if ( i->second.unjournaled_state )
241 delete i->second.unjournaled_state;
249 /** save out unjournaled state for all loggables */
251 Loggable::save_unjournaled_state ( void )
255 fp = fopen( "unjournaled", "w" );
259 DWARNING( "Could not open unjournaled state file for writing!" );
263 for ( std::map <unsigned int, Loggable::log_pair >::iterator i = _loggables.begin();
264 i != _loggables.end(); ++i )
266 if ( i->second.unjournaled_state )
268 char *s = i->second.unjournaled_state->print();
270 fprintf( fp, "0x%X set %s\n", i->first, s );
281 /** must be called after construction in create() methods */
283 Loggable::update_id ( unsigned int id )
285 /* make sure we're the last one */
286 ASSERT( _id == _log_id, "%u != %u", _id, _log_id );
287 assert( _loggables[ _id ].loggable == this );
289 _loggables[ _id ].loggable = NULL;
291 _log_id = max( _log_id, id );
293 /* return this id number to the system */
298 if ( _loggables[ _id ].loggable )
299 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() );
301 _loggables[ _id ].loggable = this;
304 /** return a pointer to a static copy of /s/ with all special characters escaped */
306 Loggable::escape ( const char *s )
311 for ( ; *s && i < sizeof( r ); ++i, ++s )
318 else if ( '"' == *s )
332 /** 'do' a message like "Audio_Region 0xF1 set :r 123" */
334 Loggable::do_this ( const char *s, bool reverse )
340 char *arguments = NULL;
342 int found = sscanf( s, "%s %X %s ", classname, &id, command );
344 ASSERT( 3 == found, "Invalid journal entry format \"%s\"", s );
346 const char *create, *destroy;
350 // sscanf( s, "%s %*X %s %*[^\n<]<< %a[^\n]", classname, command, &arguments );
351 sscanf( s, "%s %*X %s%*[^\n<]<< %a[^\n]", classname, command, &arguments );
355 DMESSAGE( "undoing \"%s\"", s );
359 sscanf( s, "%s %*X %s %a[^\n<]", classname, command, &arguments );
364 if ( ! strcmp( command, destroy ) )
366 Loggable *l = find( id );
368 /* deleting eg. a track, which contains a list of other
369 widgets, causes destroy messages to be emitted for all those
370 widgets, but when replaying the journal the destroy message
371 causes the children to be deleted also... This is a temporary
372 hack. Would it be better to queue up objects for deletion
377 else if ( ! strcmp( command, "set" ) )
379 // printf( "got set command (%s).\n", arguments );
381 Loggable *l = find( id );
383 ASSERT( l, "Unable to find object 0x%X referenced by command \"%s\"", id, s );
385 Log_Entry e( arguments );
391 else if ( ! strcmp( command, create ) )
393 Log_Entry e( arguments );
395 ASSERT( _class_map[ std::string( classname ) ], "Journal contains an object of class \"%s\", but I don't know how to create such objects.", classname );
399 Loggable *l = _class_map[ std::string( classname ) ]( e, id );
402 /* we're now creating a loggable. Apply any unjournaled
403 * state it may have had in the past under this log ID */
405 Log_Entry *e = _loggables[ id ].unjournaled_state;
419 /** Reverse the last journal transaction */
421 Loggable::undo ( void )
423 const int bufsiz = 1024;
428 long here = ftell( _fp );
430 fseek( _fp, _undo_offset, SEEK_SET );
432 backwards_fgets( buf, bufsiz, _fp );
434 if ( ! strcmp( buf, "}\n" ) )
436 DMESSAGE( "undoing block" );
439 backwards_fgets( buf, bufsiz, _fp );
451 do_this( buf, true );
453 off_t uo = ftell( _fp );
455 ASSERT( _undo_offset <= here, "WTF?" );
462 /** write a snapshot of the current state of all loggable objects to
463 * file handle /fp/ */
465 Loggable::snapshot ( FILE *fp )
469 if ( ! Loggable::_snapshot_callback )
471 DWARNING( "No snapshot callback defined" );
475 if ( ! ( _fp = fp ) )
483 Loggable::_snapshot_callback( _snapshot_callback_arg );
492 /** write a snapshot of the current state of all loggable objects to
495 Loggable::snapshot ( const char *name )
499 if ( ! ( fp = fopen( name, "w" ) ))
502 bool r = snapshot( fp );
509 /** Replace the journal with a snapshot of the current state */
511 Loggable::compact ( void )
513 fseek( _fp, 0, SEEK_SET );
514 ftruncate( fileno( _fp ), 0 );
516 if ( ! snapshot( _fp ) )
517 FATAL( "Could not write snapshot!" );
519 fseek( _fp, 0, SEEK_END );
524 /** Writes (part of) a line to the journal. Each separate line will be
525 * stored separately in _transaction until transaction is closed.
528 Loggable::log ( const char *fmt, ... )
530 static char * buf = NULL;
532 static size_t buf_size = 0;
540 buf = (char*)malloc( buf_size );
547 va_start( args, fmt );
551 size_t l = vsnprintf( buf + i, buf_size - i, fmt, args );
553 if ( l >= buf_size - i )
555 buf = (char*)realloc( buf, buf_size += (l + 1) + buf_size );
567 if ( '\n' == buf[i-1] )
569 _transaction.push( strdup( buf ) );
574 /** End the current transaction and commit it to the journal */
576 Loggable::flush ( void )
580 // printf( "error: no log file open!\n" );
582 while ( ! _transaction.empty() )
584 free( _transaction.front() );
591 int n = _transaction.size();
594 fprintf( _fp, "{\n" );
596 while ( ! _transaction.empty() )
598 char *s = _transaction.front();
603 fprintf( _fp, "\t" );
605 fprintf( _fp, "%s", s );
611 fprintf( _fp, "}\n" );
614 /* something done, reset undo index */
615 _undo_offset = ftell( _fp );
620 /** Print bidirectional journal entry */
622 Loggable::log_print( const Log_Entry *o, const Log_Entry *n ) const
628 for ( int i = 0; i < n->size(); ++i )
634 log( "%s %s%s", s, v, n->size() == i + 1 ? "" : " " );
637 if ( o && o->size() )
639 if ( n ) log( " << " );
641 for ( int i = 0; i < o->size(); ++i )
647 log( "%s %s%s", s, v, o->size() == i + 1 ? "" : " " );
654 /** Remember current object state for later comparison. *Must* be
655 * called before any user action that might change one of the object's
656 * journaled properties. */
658 Loggable::log_start ( void )
662 _old_state = new Log_Entry;
669 /** Log any change to the object's state since log_start(). */
671 Loggable::log_end ( void )
673 ASSERT( _old_state, "Programming error: log_end() called before log_start()" );
678 Log_Entry *new_state;
680 new_state = new Log_Entry;
684 if ( Log_Entry::diff( _old_state, new_state ) )
686 log( "%s 0x%X set ", class_name(), _id );
688 log_print( _old_state, new_state );
696 if ( Loggable::_level == 0 )
700 /** Log object creation. *Must* be called at the end of all public
701 * constructors for leaf classes */
704 Loggable::log_create ( void ) const
707 /* replaying, don't bother */
710 log( "%s 0x%X create ", class_name(), _id );
717 log_print( NULL, &e );
721 if ( Loggable::_level == 0 )
725 /** record this loggable's unjournaled state in memory */
727 Loggable::record_unjournaled ( void ) const
729 Log_Entry *e = new Log_Entry();
731 get_unjournaled( *e );
733 Log_Entry *le = _loggables[ _id ].unjournaled_state;
739 _loggables[ _id ].unjournaled_state = e;
742 /* don't waste space on loggables with no unjournaled properties */
743 _loggables[ _id ].unjournaled_state = NULL;
748 /** Log object destruction. *Must* be called at the beginning of the
749 * destructors of leaf classes */
751 Loggable::log_destroy ( void ) const
753 /* the unjournaled state may have changed: make a note of it. */
754 record_unjournaled();
757 /* tearing down... don't bother */
760 log( "%s 0x%X destroy << ", class_name(), _id );
766 log_print( NULL, &e );
768 if ( Loggable::_level == 0 )