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.

635 lines
13KB

  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. #include "Loggable.H"
  19. #include <stdlib.h>
  20. #include <stdio.h>
  21. #include <string.h>
  22. #include "util/file.h"
  23. #include <algorithm>
  24. using std::min;
  25. using std::max;
  26. FILE *Loggable::_fp;
  27. int Loggable::_log_id = 0;
  28. int Loggable::_level = 0;
  29. off_t Loggable::_undo_offset = 0;
  30. size_t Loggable::_loggables_size = 0;
  31. Loggable ** Loggable::_loggables;
  32. std::map <std::string, create_func*> Loggable::_class_map;
  33. std::queue <char *> Loggable::_transaction;
  34. progress_func *Loggable::_progress_callback = NULL;
  35. void *Loggable::_progress_callback_arg = NULL;
  36. /** ensure that _loggables array is big enough for /n/ elements */
  37. void
  38. Loggable::ensure_size ( size_t n )
  39. {
  40. if ( n > _loggables_size )
  41. {
  42. size_t p = 0;
  43. while ( ( (unsigned)1 << p ) < n ) ++p;
  44. size_t os = _loggables_size;
  45. _loggables_size = 1 << p ;
  46. _loggables = (Loggable**) realloc( _loggables, sizeof( Loggable ** ) * _loggables_size );
  47. memset( _loggables + os, 0, _loggables_size - os );
  48. }
  49. }
  50. /** Open the journal /filename/ and replay it, bringing the end state back into RAM */
  51. bool
  52. Loggable::open ( const char *filename )
  53. {
  54. FILE *fp;
  55. Loggable::_fp = NULL;
  56. if ( ! ( fp = fopen( filename, "a+" ) ) )
  57. {
  58. WARNING( "Could not open log file for writing!" );
  59. return false;
  60. }
  61. if ( newer( "snapshot", filename ) )
  62. {
  63. DMESSAGE( "Loading snapshot" );
  64. FILE *fp = fopen( "snapshot", "r" );
  65. replay( fp );
  66. fclose( fp );
  67. }
  68. else
  69. {
  70. DMESSAGE( "Replaying journal" );
  71. replay( fp );
  72. }
  73. fseek( fp, 0, SEEK_END );
  74. _undo_offset = ftell( fp );
  75. Loggable::_fp = fp;
  76. return true;
  77. }
  78. #include <sys/stat.h>
  79. #include <unistd.h>
  80. /** replay journal or snapshot */
  81. bool
  82. Loggable::replay ( FILE *fp )
  83. {
  84. /* FIXME: bogus */
  85. char buf[BUFSIZ];
  86. struct stat st;
  87. fstat( fileno( fp ), &st );
  88. off_t total = st.st_size;
  89. off_t current = 0;
  90. if ( _progress_callback )
  91. _progress_callback( 0, _progress_callback_arg );
  92. while ( fscanf( fp, "%[^\n]\n", buf ) == 1 )
  93. {
  94. if ( ! ( ! strcmp( buf, "{" ) || ! strcmp( buf, "}" ) ) )
  95. {
  96. if ( *buf == '\t' )
  97. do_this( buf + 1, false );
  98. else
  99. do_this( buf, false );
  100. }
  101. current = ftell( fp );
  102. if ( _progress_callback )
  103. _progress_callback( current * 100 / total, _progress_callback_arg );
  104. }
  105. if ( _progress_callback )
  106. _progress_callback( 0, _progress_callback_arg );
  107. return true;
  108. }
  109. /** close journal and delete all loggable objects, returing the systemt to a blank slate */
  110. bool
  111. Loggable::close ( void )
  112. {
  113. DMESSAGE( "closing journal and destroying all journaled objects" );
  114. if ( ! _fp )
  115. return true;
  116. fclose( _fp );
  117. _fp = NULL;
  118. snapshot( "snapshot" );
  119. for ( int i = 0; i < _log_id - 1; ++i )
  120. {
  121. Loggable ** l = &_loggables[ i ];
  122. if ( *l )
  123. {
  124. delete *l;
  125. *l = NULL;
  126. }
  127. }
  128. _log_id = 0;
  129. return true;
  130. }
  131. /** must be called after construction in create() methods */
  132. void
  133. Loggable::update_id ( int id )
  134. {
  135. /* make sure we're the last one */
  136. assert( _id == _log_id );
  137. assert( _loggables[ _id - 1 ] == this );
  138. _loggables[ _id - 1 ] = NULL;
  139. _log_id = max( _log_id, id );
  140. /* return this id number to the system */
  141. // --_log_id;
  142. _id = id;
  143. /* make sure it'll fit */
  144. ensure_size( _id );
  145. ASSERT( ! _loggables[ _id - 1 ], "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 - 1 ]->class_name(), class_name() );
  146. _loggables[ _id - 1 ] = this;
  147. }
  148. /** return a pointer to a static copy of /s/ with all special characters escaped */
  149. const char *
  150. Loggable::escape ( const char *s )
  151. {
  152. static char r[512];
  153. size_t i = 0;
  154. for ( ; *s && i < sizeof( r ); ++i, ++s )
  155. {
  156. if ( '\n' == *s )
  157. {
  158. r[ i++ ] = '\\';
  159. r[ i ] = 'n';
  160. }
  161. else if ( '"' == *s )
  162. {
  163. r[ i++ ] = '\\';
  164. r[ i ] = '"';
  165. }
  166. else
  167. r[ i ] = *s;
  168. }
  169. r[ i ] = '\0';
  170. return r;
  171. }
  172. /** 'do' a message like "Audio_Region 0xF1 set :r 123" */
  173. bool
  174. Loggable::do_this ( const char *s, bool reverse )
  175. {
  176. int id;
  177. if ( ! ( sscanf( s, "%*s %X ", &id ) > 0 ) )
  178. return false;
  179. Loggable *l = find( id );
  180. // assert( l );
  181. char classname[40];
  182. char command[40];
  183. char *arguments = NULL;
  184. const char *create, *destroy;
  185. if ( reverse )
  186. {
  187. // sscanf( s, "%s %*X %s %*[^\n<]<< %a[^\n]", classname, command, &arguments );
  188. sscanf( s, "%s %*X %s%*[^\n<]<< %a[^\n]", classname, command, &arguments );
  189. create = "destroy";
  190. destroy = "create";
  191. DMESSAGE( "undoing \"%s\"", s );
  192. }
  193. else
  194. {
  195. sscanf( s, "%s %*X %s %a[^\n<]", classname, command, &arguments );
  196. create = "create";
  197. destroy = "destroy";
  198. }
  199. if ( ! strcmp( command, destroy ) )
  200. {
  201. /* deleting eg. a track, which contains a list of other
  202. widgets, causes destroy messages to be emitted for all those
  203. widgets, but when replaying the journal the destroy message
  204. causes the children to be deleted also... This is a temporary
  205. hack. Would it be better to queue up objects for deletion
  206. (when?) */
  207. if ( l )
  208. delete l;
  209. }
  210. else if ( ! strcmp( command, "set" ) )
  211. {
  212. // printf( "got set command (%s).\n", arguments );
  213. Log_Entry e( arguments );
  214. l->log_start();
  215. l->set( e );
  216. l->log_end();
  217. }
  218. else if ( ! strcmp( command, create ) )
  219. {
  220. Log_Entry e( arguments );
  221. ASSERT( _class_map[ std::string( classname ) ], "Journal contains an object of class \"%s\", but I don't know how to create such objects.", classname );
  222. {
  223. /* create */
  224. Loggable *l = _class_map[ std::string( classname ) ]( e );
  225. l->update_id( id );
  226. l->log_create();
  227. }
  228. }
  229. if ( arguments )
  230. free( arguments );
  231. return true;
  232. }
  233. /** Reverse the last journal transaction */
  234. void
  235. Loggable::undo ( void )
  236. {
  237. const int bufsiz = 1024;
  238. char buf[bufsiz];
  239. block_start();
  240. long here = ftell( _fp );
  241. fseek( _fp, _undo_offset, SEEK_SET );
  242. backwards_fgets( buf, bufsiz, _fp );
  243. if ( ! strcmp( buf, "}\n" ) )
  244. {
  245. DMESSAGE( "undoing block" );
  246. for ( ;; )
  247. {
  248. backwards_fgets( buf, bufsiz, _fp );
  249. char *s = buf;
  250. if ( *s != '\t' )
  251. break;
  252. else
  253. ++s;
  254. do_this( s, true );
  255. }
  256. }
  257. else
  258. do_this( buf, true );
  259. off_t uo = ftell( _fp );
  260. ASSERT( _undo_offset <= here, "WTF?" );
  261. block_end();
  262. _undo_offset = uo;
  263. }
  264. /** Make all loggable ids consecutive. This invalidates any existing
  265. * journal or snapshot, so you *must* write out a new one after
  266. * performing this operation*/
  267. void
  268. Loggable::compact_ids ( void )
  269. {
  270. int id = 0;
  271. for ( int i = 0; i < _log_id; ++i )
  272. if ( _loggables[ i ] )
  273. {
  274. ++id;
  275. if ( _loggables[ id - 1 ] )
  276. continue;
  277. _loggables[ id - 1 ] = _loggables[ i ];
  278. _loggables[ i ] = NULL;
  279. _loggables[ id - 1 ]->_id = id;
  280. }
  281. _log_id = id;
  282. }
  283. /** write a snapshot of the current state of all loggable objects to
  284. * file handle /fp/ */
  285. bool
  286. Loggable::snapshot ( FILE *fp )
  287. {
  288. FILE *ofp = _fp;
  289. if ( ! ( _fp = fp ) )
  290. {
  291. _fp = ofp;
  292. return false;
  293. }
  294. block_start();
  295. for ( int i = 0; i < _log_id; ++i )
  296. {
  297. const Loggable * l = _loggables[ i ];
  298. if ( l && _class_map[ std::string( l->class_name() ) ] )
  299. l->log_create();
  300. }
  301. block_end();
  302. _fp = ofp;
  303. return true;
  304. }
  305. /** write a snapshot of the current state of all loggable objects to
  306. * file /name/ */
  307. bool
  308. Loggable::snapshot ( const char *name )
  309. {
  310. FILE *fp;
  311. if ( ! ( fp = fopen( name, "w" ) ))
  312. return false;
  313. snapshot( fp );
  314. fclose( fp );
  315. return true;
  316. }
  317. /** Replace the journal with a snapshot of the current state */
  318. void
  319. Loggable::compact ( void )
  320. {
  321. fseek( _fp, 0, SEEK_SET );
  322. ftruncate( fileno( _fp ), 0 );
  323. compact_ids();
  324. if ( ! snapshot( _fp ) )
  325. FATAL( "Could not write snapshot!" );
  326. fseek( _fp, 0, SEEK_END );
  327. }
  328. #include <stdarg.h>
  329. /** Buffered sprintf wrapper */
  330. void
  331. Loggable::log ( const char *fmt, ... )
  332. {
  333. if ( ! _fp )
  334. return;
  335. /* FIXME: bogus limit */
  336. static char buf[1024];
  337. static int i = 0;
  338. va_list args;
  339. if ( fmt )
  340. {
  341. va_start( args, fmt );
  342. i += vsprintf( buf + i, fmt, args );
  343. va_end( args );
  344. }
  345. if ( rindex( buf, '\n' ) )
  346. {
  347. _transaction.push( strdup( buf ) );
  348. i = 0;
  349. }
  350. }
  351. /** End the current transaction and commit it to the journal */
  352. void
  353. Loggable::flush ( void )
  354. {
  355. if ( ! _fp )
  356. {
  357. // printf( "error: no log file open!\n" );
  358. while ( ! _transaction.empty() )
  359. {
  360. free( _transaction.front() );
  361. _transaction.pop();
  362. }
  363. return;
  364. }
  365. int n = _transaction.size();
  366. if ( n > 1 )
  367. fprintf( _fp, "{\n" );
  368. while ( ! _transaction.empty() )
  369. {
  370. char *s = _transaction.front();
  371. _transaction.pop();
  372. if ( n > 1 )
  373. fprintf( _fp, "\t" );
  374. fprintf( _fp, "%s", s );
  375. free( s );
  376. }
  377. if ( n > 1 )
  378. fprintf( _fp, "}\n" );
  379. if ( n )
  380. /* something done, reset undo index */
  381. _undo_offset = ftell( _fp );
  382. fflush( _fp );
  383. }
  384. /** Print bidirectional journal entry */
  385. void
  386. Loggable::log_print( const Log_Entry *o, const Log_Entry *n ) const
  387. {
  388. if ( ! _fp )
  389. return;
  390. if ( n )
  391. for ( int i = 0; i < n->size(); ++i )
  392. {
  393. const char *s, *v;
  394. n->get( i, &s, &v );
  395. log( "%s %s%s", s, v, n->size() == i + 1 ? "" : " " );
  396. }
  397. if ( o && o->size() )
  398. {
  399. if ( n ) log( " << " );
  400. for ( int i = 0; i < o->size(); ++i )
  401. {
  402. const char *s, *v;
  403. o->get( i, &s, &v );
  404. log( "%s %s%s", s, v, o->size() == i + 1 ? "" : " " );
  405. }
  406. }
  407. log( "\n" );
  408. }
  409. /** Remember current object state for later comparison. *Must* be
  410. * called before any user action that might change one of the object's
  411. * journaled properties. */
  412. void
  413. Loggable::log_start ( void )
  414. {
  415. if ( ! _old_state )
  416. {
  417. _old_state = new Log_Entry;
  418. get( *_old_state );
  419. }
  420. ++_nest;
  421. }
  422. /** Log any change to the object's state since log_start(). */
  423. void
  424. Loggable::log_end ( void )
  425. {
  426. if ( --_nest > 0 )
  427. return;
  428. Log_Entry *new_state;
  429. new_state = new Log_Entry;
  430. get( *new_state );
  431. if ( Log_Entry::diff( _old_state, new_state ) )
  432. {
  433. log( "%s 0x%X set ", class_name(), _id );
  434. log_print( _old_state, new_state );
  435. }
  436. if ( new_state )
  437. delete new_state;
  438. if ( _old_state )
  439. delete _old_state;
  440. _old_state = NULL;
  441. if ( Loggable::_level == 0 )
  442. Loggable::flush();
  443. }
  444. /** Log object creation. *Must* be called at the end of all public
  445. * constructors for leaf classes */
  446. void
  447. Loggable::log_create ( void ) const
  448. {
  449. if ( ! _fp )
  450. /* replaying, don't bother */
  451. return;
  452. log( "%s 0x%X create ", class_name(), _id );
  453. Log_Entry e;
  454. get( e );
  455. if ( e.size() )
  456. log_print( NULL, &e );
  457. else
  458. log( "\n" );
  459. if ( Loggable::_level == 0 )
  460. Loggable::flush();
  461. }
  462. /** Log object destruction. *Must* be called at the beginning of the
  463. * destructors of leaf classes */
  464. void
  465. Loggable::log_destroy ( void ) const
  466. {
  467. if ( ! _fp )
  468. /* tearing down... don't bother */
  469. return;
  470. log( "%s 0x%X destroy << ", class_name(), _id );
  471. Log_Entry e;
  472. get( e );
  473. log_print( NULL, &e );
  474. if ( Loggable::_level == 0 )
  475. Loggable::flush();
  476. }