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.

753 lines
15KB

  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. #define _LOGGABLE_C
  19. #include "Loggable.H"
  20. #undef _LOGABLE_C
  21. #include <stdlib.h>
  22. #include <stdio.h>
  23. #include <stdarg.h>
  24. #include <string.h>
  25. #include <algorithm>
  26. using std::min;
  27. using std::max;
  28. FILE *Loggable::_fp;
  29. int Loggable::_log_id = 0;
  30. int Loggable::_level = 0;
  31. int Loggable::_undo_index = 1;
  32. size_t Loggable::_loggables_size = 0;
  33. Loggable ** Loggable::_loggables;
  34. std::map <std::string, create_func*> Loggable::_class_map;
  35. std::queue <char *> Loggable::_transaction;
  36. bool
  37. Loggable::open ( const char *filename )
  38. {
  39. FILE *fp;
  40. if ( ! ( fp = fopen( filename, "a+" ) ) )
  41. {
  42. WARNING( "Could not open log file for writing!" );
  43. return false;
  44. }
  45. /* replay log */
  46. {
  47. char buf[BUFSIZ];
  48. while ( fscanf( fp, "%[^\n]\n", buf ) == 1 )
  49. {
  50. if ( ! ( ! strcmp( buf, "{" ) || ! strcmp( buf, "}" ) ) )
  51. {
  52. if ( *buf == '\t' )
  53. do_this( buf + 1, false );
  54. else
  55. do_this( buf, false );
  56. }
  57. }
  58. }
  59. Loggable::_fp = fp;
  60. return true;
  61. }
  62. /** close journal and delete all loggable objects */
  63. bool
  64. Loggable::close ( void )
  65. {
  66. DMESSAGE( "closing journal and destroying all journaled objects" );
  67. if ( ! _fp )
  68. return true;
  69. fclose( _fp );
  70. _fp = NULL;
  71. for ( int i = 0; i < _log_id - 1; ++i )
  72. {
  73. Loggable ** l = &_loggables[ i ];
  74. if ( *l )
  75. {
  76. delete *l;
  77. *l = NULL;
  78. }
  79. }
  80. _log_id = 0;
  81. return true;
  82. }
  83. /** must be called after construction in create() methods */
  84. void
  85. Loggable::update_id ( int id )
  86. {
  87. /* make sure we're the last one */
  88. assert( _id == _log_id );
  89. assert( _loggables[ _id - 1 ] == this );
  90. _loggables[ _id - 1 ] = NULL;
  91. _log_id = max( _log_id, id );
  92. /* return this id number to the system */
  93. // --_log_id;
  94. _id = id;
  95. /* make sure it'll fit */
  96. ensure_size( _id );
  97. 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() );
  98. _loggables[ _id - 1 ] = this;
  99. }
  100. /** return a pointer to a static copy of /s/ with all special characters escaped */
  101. const char *
  102. Loggable::escape ( const char *s )
  103. {
  104. static char r[512];
  105. size_t i = 0;
  106. for ( ; *s && i < sizeof( r ); ++i, ++s )
  107. {
  108. if ( '\n' == *s )
  109. {
  110. r[ i++ ] = '\\';
  111. r[ i ] = 'n';
  112. }
  113. else if ( '"' == *s )
  114. {
  115. r[ i++ ] = '\\';
  116. r[ i ] = '"';
  117. }
  118. else
  119. r[ i ] = *s;
  120. }
  121. r[ i ] = '\0';
  122. return r;
  123. }
  124. /** remove escapes from string /s/ in-place */
  125. static void
  126. unescape ( char *s )
  127. {
  128. char *r = s;
  129. for ( ; *s; s++, r++ )
  130. {
  131. if ( '\\' == *s )
  132. {
  133. switch ( *(++s) )
  134. {
  135. case 'n':
  136. *r = '\n';
  137. break;
  138. case '"':
  139. *r = '"';
  140. break;
  141. default:
  142. break;
  143. }
  144. }
  145. else
  146. *r = *s;
  147. }
  148. *r = '\0';
  149. }
  150. /** sigh. parse a string of ":name value :name value" pairs into an
  151. * array of strings, one per pair */
  152. // FIXME: doesn't handle the case of :name ":foo bar", nested quotes
  153. // or other things it should.
  154. static
  155. char **
  156. parse_alist( const char *s )
  157. {
  158. // FIXME: bogus over allocation...
  159. int tl = strlen( s );
  160. char **r = (char**)malloc( sizeof( char* ) * tl );
  161. // const char *e = s + tl;
  162. const char *c = NULL;
  163. int i = 0;
  164. for ( ; ; s++ )
  165. {
  166. /* if ( *s == '\n' ) */
  167. /* break; */
  168. // if ( *s == ':' || s == e )
  169. if ( *s == ':' || *s == '\0' )
  170. {
  171. if ( c )
  172. {
  173. int l = s - c;
  174. char *pair = (char*)malloc( l + 1 );
  175. /* remove trailing space */
  176. if ( c[ l - 1 ] == ' ' )
  177. --l;
  178. strncpy( pair, c, l );
  179. pair[ l ] = '\0';
  180. r[ i++ ] = pair;
  181. /* split */
  182. strtok( pair, " " );
  183. /* remove quotes */
  184. char *v = pair + strlen( pair ) + 1;
  185. unescape( v );
  186. if ( *v == '"' )
  187. {
  188. // v++;
  189. if ( v[ strlen( v ) - 1 ] != '"' )
  190. WARNING( "invalid quoting in log entry!" );
  191. else
  192. {
  193. v[ strlen( v ) - 1 ] = '\0';
  194. memmove( v, v + 1, strlen( v ) + 1 );
  195. }
  196. }
  197. }
  198. c = s;
  199. if ( *s == '\0' )
  200. break;
  201. }
  202. }
  203. r[ i ] = NULL;
  204. return r;
  205. }
  206. static
  207. void free_sa ( char **sa )
  208. {
  209. char **a = sa;
  210. for ( ; *a; a++ )
  211. free( *a );
  212. free( sa );
  213. }
  214. /** 'do' a message like "Audio_Region 0xF1 set :r 123" */
  215. bool
  216. Loggable::do_this ( const char *s, bool reverse )
  217. {
  218. int id;
  219. if ( ! ( sscanf( s, "%*s %X ", &id ) > 0 ) )
  220. return false;
  221. Loggable *l = find( id );
  222. // assert( l );
  223. char classname[40];
  224. char command[40];
  225. char *arguments = NULL;
  226. const char *create, *destroy;
  227. if ( reverse )
  228. {
  229. // sscanf( s, "%s %*X %s %*[^\n<]<< %a[^\n]", classname, command, &arguments );
  230. sscanf( s, "%s %*X %s%*[^\n<]<< %a[^\n]", classname, command, &arguments );
  231. create = "destroy";
  232. destroy = "create";
  233. }
  234. else
  235. {
  236. sscanf( s, "%s %*X %s %a[^\n<]", classname, command, &arguments );
  237. create = "create";
  238. destroy = "destroy";
  239. }
  240. if ( ! strcmp( command, destroy ) )
  241. {
  242. /* deleting eg. a track, which contains a list of other
  243. widgets, causes destroy messages to be emitted for all those
  244. widgets, but when replaying the journal the destroy message
  245. causes the children to be deleted also... This is a temporary
  246. hack. Would it be better to queue up objects for deletion
  247. (when?) */
  248. if ( l )
  249. delete l;
  250. }
  251. else if ( ! strcmp( command, "set" ) )
  252. {
  253. // printf( "got set command (%s).\n", arguments );
  254. char **sa = parse_alist( arguments );
  255. Log_Entry e( sa );
  256. l->log_start();
  257. l->set( e );
  258. l->log_end();
  259. }
  260. else if ( ! strcmp( command, create ) )
  261. {
  262. char **sa = NULL;
  263. if ( arguments )
  264. sa = parse_alist( arguments );
  265. Log_Entry e( sa );
  266. ASSERT( _class_map[ std::string( classname ) ], "Journal contains an object of class \"%s\", but I don't know how to create such objects.", classname );
  267. {
  268. /* create */
  269. Loggable *l = _class_map[ std::string( classname ) ]( e );
  270. l->update_id( id );
  271. l->log_create();
  272. }
  273. }
  274. if ( arguments )
  275. free( arguments );
  276. return true;
  277. }
  278. void
  279. Loggable::undo ( void )
  280. {
  281. char *buf = new char[ BUFSIZ ];
  282. // fflush( _fp );
  283. /* FIXME: handle more than the first block!!! */
  284. fseek( _fp, 0, SEEK_END );
  285. size_t len = ftell( _fp );
  286. fseek( _fp, 0 - (BUFSIZ > len ? len : BUFSIZ), SEEK_END );
  287. len = fread( buf, 1, BUFSIZ, _fp );
  288. char *s = buf + len - 1;
  289. int i = 1;
  290. /* move back _undo_index items from the end */
  291. for ( int j = _undo_index; j-- ; )
  292. for ( --s; *s && s >= buf; --s, ++i )
  293. {
  294. if ( *s == '\n' )
  295. {
  296. if ( *(s + 1) == '\t' )
  297. continue;
  298. if ( *(s + 1) == '}' )
  299. {
  300. *(s + 1) = NULL;
  301. continue;
  302. }
  303. break;
  304. }
  305. }
  306. s++;
  307. buf[ len ] = NULL;
  308. if ( ! strlen( s ) )
  309. {
  310. WARNING( "corrupt undo file or no undo entries." );
  311. return;
  312. }
  313. char *b = s;
  314. s += strlen( s ) - 1;
  315. if ( strtok( b, "\n" ) == NULL )
  316. FATAL( "empty undo transaction!\n" );
  317. int n = 1;
  318. while ( strtok( NULL, "\n" ) )
  319. ++n;
  320. int ui = _undo_index;
  321. block_start();
  322. if ( strcmp( b, "{" ) )
  323. {
  324. /* It's a single undo, get rid of trailing messages in this block */
  325. n = 1;
  326. s = b + 2;
  327. s += strlen( s ) - 1;
  328. }
  329. while ( n-- )
  330. {
  331. while ( s >= b && *(--s) );
  332. s++;
  333. if ( ! strcmp( s, "{" ) )
  334. break;
  335. if ( *s == '\t' )
  336. s++;
  337. DMESSAGE( "undoing \"%s\"", s );
  338. do_this( s, true );
  339. s -= 2;
  340. }
  341. block_end();
  342. _undo_index = ui + 2;
  343. delete[] buf;
  344. }
  345. /** write a snapshot of the state of all loggable objects, sufficient
  346. * for later reconstruction, to /fp/ */
  347. bool
  348. Loggable::snapshot( FILE *fp )
  349. {
  350. FILE *ofp = _fp;
  351. if ( ! ( _fp = fp ) )
  352. {
  353. _fp = ofp;
  354. return false;
  355. }
  356. /* first, make all ids consecutive */
  357. int id = 0;
  358. for ( int i = 0; i < _log_id; ++i )
  359. if ( _loggables[ i ] )
  360. {
  361. ++id;
  362. if ( _loggables[ id - 1 ] )
  363. continue;
  364. _loggables[ id - 1 ] = _loggables[ i ];
  365. _loggables[ i ] = NULL;
  366. _loggables[ id - 1 ]->_id = id;
  367. }
  368. _log_id = id;
  369. block_start();
  370. for ( int i = 0; i < _log_id; ++i )
  371. {
  372. const Loggable * l = _loggables[ i ];
  373. if ( l && _class_map[ std::string( l->class_name() ) ] )
  374. l->log_create();
  375. }
  376. block_end();
  377. _fp = ofp;
  378. return true;
  379. }
  380. void
  381. Loggable::compact ( void )
  382. {
  383. fseek( _fp, 0, SEEK_SET );
  384. ftruncate( fileno( _fp ), 0 );
  385. snapshot( _fp );
  386. _undo_index = 1;
  387. }
  388. void
  389. Loggable::log ( const char *fmt, ... )
  390. {
  391. /* FIXME: bogus limit */
  392. static char buf[1024];
  393. static int i = 0;
  394. va_list args;
  395. if ( fmt )
  396. {
  397. va_start( args, fmt );
  398. i += vsprintf( buf + i, fmt, args );
  399. va_end( args );
  400. }
  401. if ( rindex( buf, '\n' ) )
  402. {
  403. _transaction.push( strdup( buf ) );
  404. i = 0;
  405. }
  406. }
  407. void
  408. Loggable::flush ( void )
  409. {
  410. if ( ! _fp )
  411. {
  412. // printf( "error: no log file open!\n" );
  413. while ( ! _transaction.empty() )
  414. {
  415. free( _transaction.front() );
  416. _transaction.pop();
  417. }
  418. return;
  419. }
  420. int n = _transaction.size();
  421. if ( n )
  422. /* something done, reset undo index */
  423. _undo_index = 1;
  424. if ( n > 1 )
  425. fprintf( _fp, "{\n" );
  426. while ( ! _transaction.empty() )
  427. {
  428. char *s = _transaction.front();
  429. _transaction.pop();
  430. if ( n > 1 )
  431. fprintf( _fp, "\t" );
  432. fprintf( _fp, "%s", s );
  433. free( s );
  434. }
  435. if ( n > 1 )
  436. fprintf( _fp, "}\n" );
  437. fflush( _fp );
  438. }
  439. void
  440. Loggable::log_print( char **o, char **n ) const
  441. {
  442. if ( n )
  443. for ( ; *n; n++ )
  444. log( "%s %s%s", *n, *n + strlen( *n ) + 1, *(n + 1) ? " " : "" );
  445. if ( o && *o )
  446. {
  447. if ( n ) log( " << " );
  448. for ( ; *o; o++ )
  449. log( "%s %s%s", *o, *o + strlen( *o ) + 1, *(o + 1) ? " " : "" );
  450. }
  451. log( "\n" );
  452. }
  453. /** compare elements of dumps s1 and s2, removing those elements
  454. of dst which are not changed from src */
  455. static
  456. bool
  457. log_diff ( char **sa1, char **sa2 )
  458. {
  459. if ( ! sa1 )
  460. return true;
  461. int w = 0;
  462. for ( int i = 0; sa1[ i ]; ++i )
  463. {
  464. const char *v1 = sa1[ i ] + strlen( sa1[ i ] ) + 1;
  465. const char *v2 = sa2[ i ] + strlen( sa2[ i ] ) + 1;
  466. if ( ! strcmp( sa1[ i ], sa2[ i ] ) && ! strcmp( v1, v2 ) )
  467. {
  468. free( sa2[ i ] );
  469. free( sa1[ i ] );
  470. }
  471. else
  472. {
  473. sa2[ w ] = sa2[ i ];
  474. sa1[ w ] = sa1[ i ];
  475. w++;
  476. }
  477. }
  478. sa1[ w ] = NULL;
  479. sa2[ w ] = NULL;
  480. return w == 0 ? false : true;
  481. }
  482. void
  483. Loggable::log_start ( void )
  484. {
  485. if ( ! _old_state )
  486. {
  487. Log_Entry e;
  488. get( e );
  489. _old_state = e.sa();
  490. }
  491. ++_nest;
  492. }
  493. void
  494. Loggable::log_end ( void )
  495. {
  496. if ( --_nest > 0 )
  497. return;
  498. char **_new_state;
  499. {
  500. Log_Entry e;
  501. get( e );
  502. _new_state = e.sa();
  503. }
  504. if ( log_diff( _old_state, _new_state ) )
  505. {
  506. // indent();
  507. log( "%s 0x%X set ", class_name(), _id );
  508. log_print( _old_state, _new_state );
  509. }
  510. if ( _new_state )
  511. free_sa( _new_state );
  512. if ( _old_state )
  513. free_sa( _old_state );
  514. _old_state = NULL;
  515. if ( Loggable::_level == 0 )
  516. Loggable::flush();
  517. }
  518. void
  519. Loggable::log_create ( void ) const
  520. {
  521. // indent();
  522. /* if ( Loggable::_level ) */
  523. /* { */
  524. /* /\* defer until the block ends--this is really only required for capturing... *\/ */
  525. /* } */
  526. log( "%s 0x%X create ", class_name(), _id );
  527. char **sa;
  528. Log_Entry e;
  529. get( e );
  530. sa = e.sa();
  531. if ( sa )
  532. {
  533. log_print( NULL, sa );
  534. free_sa( sa );
  535. }
  536. else
  537. log( "\n" );
  538. if ( Loggable::_level == 0 )
  539. Loggable::flush();
  540. }
  541. void
  542. Loggable::log_destroy ( void ) const
  543. {
  544. if ( ! _fp )
  545. /* tearing down... don't bother */
  546. return;
  547. log( "%s 0x%X destroy << ", class_name(), _id );
  548. char **sa;
  549. Log_Entry e;
  550. get( e );
  551. sa = e.sa();
  552. // log_print( sa, NULL );
  553. log_print( NULL, sa );
  554. free_sa( sa );
  555. if ( Loggable::_level == 0 )
  556. Loggable::flush();
  557. }