Assists music production by grouping standalone programs into sessions. Community version of "Non Session Manager".
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

827 lines
18KB

  1. /*******************************************************************************/
  2. /* Copyright (C) 2008 Jonathan Moore Liles */
  3. /* */
  4. /* This program is free software; you can redistribute it and/or modify it */
  5. /* under the terms of the GNU General Public License as published by the */
  6. /* Free Software Foundation; either version 2 of the License, or (at your */
  7. /* option) any later version. */
  8. /* */
  9. /* This program is distributed in the hope that it will be useful, but WITHOUT */
  10. /* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */
  11. /* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for */
  12. /* more details. */
  13. /* */
  14. /* You should have received a copy of the GNU General Public License along */
  15. /* with This program; see the file COPYING. If not,write to the Free Software */
  16. /* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
  17. /*******************************************************************************/
  18. /* This class handles all journaling. All journaled objects must
  19. inherit from Loggable as well as define a few special methods (via
  20. macros), get and set methods, and have contructors and destructors
  21. that call log_create() and log_destroy() in the appropriate
  22. order. Any action that might affect multiple loggable objects
  23. *must* be braced by calls to Loggable::block_start() and
  24. Loggable::block_end() in order for Undo to work properly. */
  25. #include "Loggable.H"
  26. #include <stdlib.h>
  27. #include <stdio.h>
  28. #include <string.h>
  29. #include "file.h"
  30. // #include "const.h"
  31. #include "debug.h"
  32. #include "Mutex.H"
  33. #include <algorithm>
  34. using std::min;
  35. using std::max;
  36. FILE *Loggable::_fp;
  37. unsigned int Loggable::_log_id = 0;
  38. int Loggable::_level = 0;
  39. int Loggable::_dirty = 0;
  40. off_t Loggable::_undo_offset = 0;
  41. std::map <unsigned int, Loggable::log_pair > Loggable::_loggables;
  42. std::map <std::string, create_func*> Loggable::_class_map;
  43. std::queue <char *> Loggable::_transaction;
  44. progress_func *Loggable::_progress_callback = NULL;
  45. void *Loggable::_progress_callback_arg = NULL;
  46. snapshot_func *Loggable::_snapshot_callback = NULL;
  47. void *Loggable::_snapshot_callback_arg = NULL;
  48. dirty_func *Loggable::_dirty_callback = NULL;
  49. void *Loggable::_dirty_callback_arg = NULL;
  50. static Mutex _lock;
  51. Loggable::~Loggable ( )
  52. {
  53. Locker lock( _lock );;
  54. _loggables[ _id ].loggable = NULL;
  55. }
  56. void
  57. Loggable::block_start ( void )
  58. {
  59. Locker lock( _lock );;
  60. ++Loggable::_level;
  61. }
  62. void
  63. Loggable::block_end ( void )
  64. {
  65. Locker lock( _lock );;
  66. --Loggable::_level;
  67. ASSERT( Loggable::_level >= 0, "Programming error" );
  68. if ( Loggable::_level == 0 )
  69. flush();
  70. }
  71. Loggable *
  72. Loggable::find ( unsigned int id )
  73. {
  74. if ( _relative_id )
  75. id += _relative_id;
  76. return _loggables[ id ].loggable;
  77. }
  78. /** Open the journal /filename/ and replay it, bringing the end state back into RAM */
  79. bool
  80. Loggable::open ( const char *filename )
  81. {
  82. FILE *fp;
  83. Loggable::_fp = NULL;
  84. if ( ! ( fp = fopen( filename, "a+" ) ) )
  85. {
  86. WARNING( "Could not open log file for writing!" );
  87. return false;
  88. }
  89. load_unjournaled_state();
  90. if ( newer( "snapshot", filename ) )
  91. {
  92. MESSAGE( "Loading snapshot" );
  93. FILE *fp = fopen( "snapshot", "r" );
  94. replay( fp );
  95. fclose( fp );
  96. }
  97. else
  98. {
  99. MESSAGE( "Replaying journal" );
  100. replay( fp );
  101. }
  102. fseek( fp, 0, SEEK_END );
  103. _undo_offset = ftell( fp );
  104. Loggable::_fp = fp;
  105. return true;
  106. }
  107. bool
  108. Loggable::load_unjournaled_state ( void )
  109. {
  110. FILE *fp;
  111. fp = fopen( "unjournaled", "r" );
  112. if ( ! fp )
  113. {
  114. DWARNING( "Could not open unjournaled state file for reading" );
  115. return false;
  116. }
  117. unsigned int id;
  118. char buf[BUFSIZ];
  119. while ( fscanf( fp, "%X set %[^\n]\n", &id, buf ) == 2 )
  120. _loggables[ id ].unjournaled_state = new Log_Entry( buf );
  121. fclose( fp );
  122. return true;
  123. }
  124. #include <sys/stat.h>
  125. #include <unistd.h>
  126. /** replay journal or snapshot */
  127. bool
  128. Loggable::replay ( const char *file )
  129. {
  130. if ( FILE *fp = fopen( file, "r" ) )
  131. {
  132. bool r = replay( fp );
  133. fclose( fp );
  134. return r;
  135. }
  136. else
  137. return false;
  138. }
  139. /** replay journal or snapshot */
  140. bool
  141. Loggable::replay ( FILE *fp )
  142. {
  143. /* FIXME: bogus */
  144. char buf[BUFSIZ];
  145. struct stat st;
  146. fstat( fileno( fp ), &st );
  147. off_t total = st.st_size;
  148. off_t current = 0;
  149. if ( _progress_callback )
  150. _progress_callback( 0, _progress_callback_arg );
  151. while ( fscanf( fp, "%[^\n]\n", buf ) == 1 )
  152. {
  153. if ( ! ( ! strcmp( buf, "{" ) || ! strcmp( buf, "}" ) ) )
  154. {
  155. if ( *buf == '\t' )
  156. do_this( buf + 1, false );
  157. else
  158. do_this( buf, false );
  159. }
  160. current = ftell( fp );
  161. if ( _progress_callback )
  162. _progress_callback( current * 100 / total, _progress_callback_arg );
  163. }
  164. if ( _progress_callback )
  165. _progress_callback( 0, _progress_callback_arg );
  166. clear_dirty();
  167. return true;
  168. }
  169. /** close journal and delete all loggable objects, returing the systemt to a blank slate */
  170. bool
  171. Loggable::close ( void )
  172. {
  173. DMESSAGE( "closing journal and destroying all journaled objects" );
  174. if ( _fp )
  175. {
  176. fclose( _fp );
  177. _fp = NULL;
  178. }
  179. if ( ! snapshot( "snapshot" ) )
  180. WARNING( "Failed to create snapshot" );
  181. for ( std::map <unsigned int, Loggable::log_pair >::iterator i = _loggables.begin();
  182. i != _loggables.end(); ++i )
  183. if ( i->second.loggable )
  184. delete i->second.loggable;
  185. save_unjournaled_state();
  186. for ( std::map <unsigned int, Loggable::log_pair >::iterator i = _loggables.begin();
  187. i != _loggables.end(); ++i )
  188. if ( i->second.unjournaled_state )
  189. delete i->second.unjournaled_state;
  190. _loggables.clear();
  191. return true;
  192. }
  193. /** save out unjournaled state for all loggables */
  194. bool
  195. Loggable::save_unjournaled_state ( void )
  196. {
  197. FILE *fp;
  198. fp = fopen( "unjournaled", "w" );
  199. if ( ! fp )
  200. {
  201. DWARNING( "Could not open unjournaled state file for writing!" );
  202. return false;
  203. }
  204. for ( std::map <unsigned int, Loggable::log_pair >::iterator i = _loggables.begin();
  205. i != _loggables.end(); ++i )
  206. {
  207. if ( i->second.unjournaled_state )
  208. {
  209. char *s = i->second.unjournaled_state->print();
  210. fprintf( fp, "0x%X set %s\n", i->first, s );
  211. free( s );
  212. }
  213. }
  214. fclose( fp );
  215. return true;
  216. }
  217. /** must be called after construction in create() methods */
  218. void
  219. Loggable::update_id ( unsigned int id )
  220. {
  221. /* make sure we're the last one */
  222. ASSERT( _id == _log_id, "%u != %u", _id, _log_id );
  223. assert( _loggables[ _id ].loggable == this );
  224. _loggables[ _id ].loggable = NULL;
  225. _log_id = max( _log_id, id );
  226. /* return this id number to the system */
  227. // --_log_id;
  228. _id = id;
  229. if ( _loggables[ _id ].loggable )
  230. 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() );
  231. _loggables[ _id ].loggable = this;
  232. }
  233. /** return a pointer to a static copy of /s/ with all special characters escaped */
  234. const char *
  235. Loggable::escape ( const char *s )
  236. {
  237. static char r[512];
  238. size_t i = 0;
  239. for ( ; *s && i < sizeof( r ); ++i, ++s )
  240. {
  241. if ( '\n' == *s )
  242. {
  243. r[ i++ ] = '\\';
  244. r[ i ] = 'n';
  245. }
  246. else if ( '"' == *s )
  247. {
  248. r[ i++ ] = '\\';
  249. r[ i ] = '"';
  250. }
  251. else
  252. r[ i ] = *s;
  253. }
  254. r[ i ] = '\0';
  255. return r;
  256. }
  257. unsigned int Loggable::_relative_id = 0;
  258. /* calls to do_this() between invocation of this method and
  259. * end_relative_id_mode() will have all their IDs made relative to the
  260. * highest available ID at this time of this call. Non-Mixer uses
  261. * this to allow importing of module chains */
  262. void
  263. Loggable::begin_relative_id_mode ( void )
  264. {
  265. _relative_id = ++_log_id;
  266. }
  267. void
  268. Loggable::end_relative_id_mode ( void )
  269. {
  270. _relative_id = 0;
  271. }
  272. /** 'do' a message like "Audio_Region 0xF1 set :r 123" */
  273. bool
  274. Loggable::do_this ( const char *s, bool reverse )
  275. {
  276. unsigned int id = 0;
  277. char classname[40];
  278. char command[40];
  279. char *arguments = NULL;
  280. int found = sscanf( s, "%s %X %s ", classname, &id, command );
  281. ASSERT( 3 == found, "Invalid journal entry format \"%s\"", s );
  282. const char *create, *destroy;
  283. if ( reverse )
  284. {
  285. // sscanf( s, "%s %*X %s %*[^\n<]<< %a[^\n]", classname, command, &arguments );
  286. sscanf( s, "%s %*X %s%*[^\n<]<< %a[^\n]", classname, command, &arguments );
  287. create = "destroy";
  288. destroy = "create";
  289. DMESSAGE( "undoing \"%s\"", s );
  290. }
  291. else
  292. {
  293. sscanf( s, "%s %*X %s %a[^\n<]", classname, command, &arguments );
  294. create = "create";
  295. destroy = "destroy";
  296. }
  297. if ( ! strcmp( command, destroy ) )
  298. {
  299. Loggable *l = find( id );
  300. /* deleting eg. a track, which contains a list of other
  301. widgets, causes destroy messages to be emitted for all those
  302. widgets, but when replaying the journal the destroy message
  303. causes the children to be deleted also... This is a temporary
  304. hack. Would it be better to queue up objects for deletion
  305. (when?) */
  306. if ( l )
  307. delete l;
  308. }
  309. else if ( ! strcmp( command, "set" ) )
  310. {
  311. // printf( "got set command (%s).\n", arguments );
  312. Loggable *l = find( id );
  313. ASSERT( l, "Unable to find object 0x%X referenced by command \"%s\"", id, s );
  314. Log_Entry e( arguments );
  315. l->log_start();
  316. l->set( e );
  317. l->log_end();
  318. }
  319. else if ( ! strcmp( command, create ) )
  320. {
  321. Log_Entry e( arguments );
  322. ASSERT( _class_map[ std::string( classname ) ], "Journal contains an object of class \"%s\", but I don't know how to create such objects.", classname );
  323. {
  324. if ( _relative_id )
  325. id += _relative_id;
  326. /* create */
  327. Loggable *l = _class_map[ std::string( classname ) ]( e, id );
  328. l->log_create();
  329. /* we're now creating a loggable. Apply any unjournaled
  330. * state it may have had in the past under this log ID */
  331. Log_Entry *e = _loggables[ id ].unjournaled_state;
  332. if ( e )
  333. l->set( *e );
  334. }
  335. }
  336. if ( arguments )
  337. free( arguments );
  338. return true;
  339. }
  340. /** Reverse the last journal transaction */
  341. void
  342. Loggable::undo ( void )
  343. {
  344. const int bufsiz = 1024;
  345. char buf[bufsiz];
  346. block_start();
  347. long here = ftell( _fp );
  348. fseek( _fp, _undo_offset, SEEK_SET );
  349. backwards_fgets( buf, bufsiz, _fp );
  350. if ( ! strcmp( buf, "}\n" ) )
  351. {
  352. DMESSAGE( "undoing block" );
  353. for ( ;; )
  354. {
  355. backwards_fgets( buf, bufsiz, _fp );
  356. char *s = buf;
  357. if ( *s != '\t' )
  358. break;
  359. else
  360. ++s;
  361. do_this( s, true );
  362. }
  363. }
  364. else
  365. do_this( buf, true );
  366. off_t uo = ftell( _fp );
  367. ASSERT( _undo_offset <= here, "WTF?" );
  368. block_end();
  369. _undo_offset = uo;
  370. }
  371. /** write a snapshot of the current state of all loggable objects to
  372. * file handle /fp/ */
  373. bool
  374. Loggable::snapshot ( FILE *fp )
  375. {
  376. FILE *ofp = _fp;
  377. if ( ! Loggable::_snapshot_callback )
  378. {
  379. DWARNING( "No snapshot callback defined" );
  380. return false;
  381. }
  382. if ( ! ( _fp = fp ) )
  383. {
  384. _fp = ofp;
  385. return false;
  386. }
  387. block_start();
  388. Loggable::_snapshot_callback( _snapshot_callback_arg );
  389. block_end();
  390. _fp = ofp;
  391. clear_dirty();
  392. return true;
  393. }
  394. /** write a snapshot of the current state of all loggable objects to
  395. * file /name/ */
  396. bool
  397. Loggable::snapshot ( const char *name )
  398. {
  399. FILE *fp;
  400. if ( ! ( fp = fopen( name, "w" ) ))
  401. return false;
  402. bool r = snapshot( fp );
  403. fclose( fp );
  404. return r;
  405. }
  406. /** Replace the journal with a snapshot of the current state */
  407. void
  408. Loggable::compact ( void )
  409. {
  410. fseek( _fp, 0, SEEK_SET );
  411. ftruncate( fileno( _fp ), 0 );
  412. if ( ! snapshot( _fp ) )
  413. FATAL( "Could not write snapshot!" );
  414. fseek( _fp, 0, SEEK_END );
  415. }
  416. #include <stdarg.h>
  417. /** Writes (part of) a line to the journal. Each separate line will be
  418. * stored separately in _transaction until transaction is closed.
  419. */
  420. void
  421. Loggable::log ( const char *fmt, ... )
  422. {
  423. Locker lock( _lock );
  424. static char * buf = NULL;
  425. static size_t i = 0;
  426. static size_t buf_size = 0;
  427. if ( ! _fp )
  428. return;
  429. if ( NULL == buf )
  430. {
  431. buf_size = 1024;
  432. buf = (char*)malloc( buf_size );
  433. }
  434. va_list args;
  435. if ( fmt )
  436. {
  437. va_start( args, fmt );
  438. for ( ;; )
  439. {
  440. size_t l = vsnprintf( buf + i, buf_size - i, fmt, args );
  441. if ( l >= buf_size - i )
  442. {
  443. buf = (char*)realloc( buf, buf_size += (l + 1) + buf_size );
  444. }
  445. else
  446. {
  447. i += l;
  448. break;
  449. }
  450. }
  451. va_end( args );
  452. }
  453. if ( '\n' == buf[i-1] )
  454. {
  455. _transaction.push( strdup( buf ) );
  456. i = 0;
  457. }
  458. }
  459. /** End the current transaction and commit it to the journal */
  460. void
  461. Loggable::flush ( void )
  462. {
  463. if ( ! _fp )
  464. {
  465. // printf( "error: no log file open!\n" );
  466. while ( ! _transaction.empty() )
  467. {
  468. free( _transaction.front() );
  469. _transaction.pop();
  470. }
  471. return;
  472. }
  473. int n = _transaction.size();
  474. if ( n > 1 )
  475. fprintf( _fp, "{\n" );
  476. while ( ! _transaction.empty() )
  477. {
  478. char *s = _transaction.front();
  479. _transaction.pop();
  480. if ( n > 1 )
  481. fprintf( _fp, "\t" );
  482. fprintf( _fp, "%s", s );
  483. free( s );
  484. }
  485. if ( n > 1 )
  486. fprintf( _fp, "}\n" );
  487. if ( n )
  488. /* something done, reset undo index */
  489. _undo_offset = ftell( _fp );
  490. fflush( _fp );
  491. }
  492. /** Print bidirectional journal entry */
  493. void
  494. Loggable::log_print( const Log_Entry *o, const Log_Entry *n ) const
  495. {
  496. if ( ! _fp )
  497. return;
  498. if ( n )
  499. for ( int i = 0; i < n->size(); ++i )
  500. {
  501. const char *s, *v;
  502. n->get( i, &s, &v );
  503. log( "%s %s%s", s, v, n->size() == i + 1 ? "" : " " );
  504. }
  505. if ( o && o->size() )
  506. {
  507. if ( n ) log( " << " );
  508. for ( int i = 0; i < o->size(); ++i )
  509. {
  510. const char *s, *v;
  511. o->get( i, &s, &v );
  512. log( "%s %s%s", s, v, o->size() == i + 1 ? "" : " " );
  513. }
  514. }
  515. log( "\n" );
  516. }
  517. /** Remember current object state for later comparison. *Must* be
  518. * called before any user action that might change one of the object's
  519. * journaled properties. */
  520. void
  521. Loggable::log_start ( void )
  522. {
  523. Locker lock( _lock );;
  524. if ( ! _old_state )
  525. {
  526. _old_state = new Log_Entry;
  527. get( *_old_state );
  528. }
  529. ++_nest;
  530. }
  531. /** Log any change to the object's state since log_start(). */
  532. void
  533. Loggable::log_end ( void )
  534. {
  535. Locker lock( _lock );;
  536. ASSERT( _old_state, "Programming error: log_end() called before log_start()" );
  537. if ( --_nest > 0 )
  538. return;
  539. Log_Entry *new_state;
  540. new_state = new Log_Entry;
  541. get( *new_state );
  542. if ( Log_Entry::diff( _old_state, new_state ) )
  543. {
  544. log( "%s 0x%X set ", class_name(), _id );
  545. log_print( _old_state, new_state );
  546. set_dirty();
  547. }
  548. delete new_state;
  549. delete _old_state;
  550. _old_state = NULL;
  551. if ( Loggable::_level == 0 )
  552. Loggable::flush();
  553. }
  554. /** Log object creation. *Must* be called at the end of all public
  555. * constructors for leaf classes */
  556. void
  557. Loggable::log_create ( void ) const
  558. {
  559. Locker lock( _lock );;
  560. set_dirty();
  561. if ( ! _fp )
  562. /* replaying, don't bother */
  563. return;
  564. log( "%s 0x%X create ", class_name(), _id );
  565. Log_Entry e;
  566. get( e );
  567. if ( e.size() )
  568. log_print( NULL, &e );
  569. else
  570. log( "\n" );
  571. if ( Loggable::_level == 0 )
  572. Loggable::flush();
  573. }
  574. /** record this loggable's unjournaled state in memory */
  575. void
  576. Loggable::record_unjournaled ( void ) const
  577. {
  578. Log_Entry *e = new Log_Entry();
  579. get_unjournaled( *e );
  580. Log_Entry *le = _loggables[ _id ].unjournaled_state;
  581. if ( le )
  582. delete le;
  583. if ( e->size() )
  584. _loggables[ _id ].unjournaled_state = e;
  585. else
  586. {
  587. /* don't waste space on loggables with no unjournaled properties */
  588. _loggables[ _id ].unjournaled_state = NULL;
  589. delete e;
  590. }
  591. }
  592. /** Log object destruction. *Must* be called at the beginning of the
  593. * destructors of leaf classes */
  594. void
  595. Loggable::log_destroy ( void ) const
  596. {
  597. Locker lock( _lock );;
  598. /* the unjournaled state may have changed: make a note of it. */
  599. record_unjournaled();
  600. set_dirty();
  601. if ( ! _fp )
  602. /* tearing down... don't bother */
  603. return;
  604. log( "%s 0x%X destroy << ", class_name(), _id );
  605. Log_Entry e;
  606. get( e );
  607. log_print( NULL, &e );
  608. if ( Loggable::_level == 0 )
  609. Loggable::flush();
  610. }