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.

611 lines
12KB

  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. FILE *Loggable::_fp;
  26. int Loggable::_log_id = 0;
  27. int Loggable::_level = 0;
  28. int Loggable::_undo_index = 1;
  29. vector <Loggable *> Loggable::_loggables;
  30. map <string, create_func*> Loggable::_class_map;
  31. queue <char *> Loggable::_transaction;
  32. bool
  33. Loggable::open ( const char *filename )
  34. {
  35. FILE *fp;
  36. if ( ! ( fp = fopen( filename, "a+" ) ) )
  37. {
  38. printf( "Could not open log file for writing!" );
  39. return false;
  40. }
  41. /* replay log */
  42. {
  43. char buf[BUFSIZ];
  44. while ( fscanf( fp, "%[^\n]\n", buf ) == 1 )
  45. {
  46. if ( ! ( ! strcmp( buf, "{" ) || ! strcmp( buf, "}" ) ) )
  47. {
  48. if ( *buf == '\t' )
  49. do_this( buf + 1, false );
  50. else
  51. do_this( buf, false );
  52. }
  53. }
  54. }
  55. Loggable::_fp = fp;
  56. return true;
  57. }
  58. /** sigh. parse a string of ":name value :name value" pairs into an
  59. * array of strings, one per pair */
  60. // FIXME: doesn't handle the case of :name ":foo bar", nested quotes
  61. // or other things it should. Also, quotes should be removed here, not
  62. // in client code.
  63. static
  64. char **
  65. parse_alist( const char *s )
  66. {
  67. // FIXME: bogus over allocation...
  68. int tl = strlen( s );
  69. char **r = (char**)malloc( sizeof( char* ) * tl );
  70. const char *e = s + tl;
  71. const char *c = NULL;
  72. int i = 0;
  73. for ( ; ; s++ )
  74. {
  75. /* if ( *s == '\n' ) */
  76. /* break; */
  77. // if ( *s == ':' || s == e )
  78. if ( *s == ':' || *s == '\0' )
  79. {
  80. if ( c )
  81. {
  82. int l = s - c;
  83. char *pair = (char*)malloc( l + 1 );
  84. /* remove trailing space */
  85. if ( c[ l - 1 ] == ' ' )
  86. --l;
  87. strncpy( pair, c, l );
  88. pair[ l ] = '\0';
  89. r[ i++ ] = pair;
  90. /* split */
  91. strtok( pair, " " );
  92. /* remove quotes */
  93. char *v = pair + strlen( pair ) + 1;
  94. if ( *v == '"' )
  95. {
  96. // v++;
  97. if ( v[ strlen( v ) - 1 ] != '"' )
  98. printf( "error: invalid quoting in log entry!\n" );
  99. else
  100. {
  101. v[ strlen( v ) - 1 ] = '\0';
  102. memmove( v, v + 1, strlen( v ) + 1 );
  103. }
  104. }
  105. }
  106. c = s;
  107. if ( *s == '\0' )
  108. break;
  109. }
  110. }
  111. r[ i ] = NULL;
  112. return r;
  113. }
  114. static
  115. void free_sa ( char **sa )
  116. {
  117. char **a = sa;
  118. for ( ; *a; a++ )
  119. free( *a );
  120. free( sa );
  121. }
  122. /** 'do' a message like "Region 0xF1 set :r 123" */
  123. bool
  124. Loggable::do_this ( const char *s, bool reverse )
  125. {
  126. int id;
  127. if ( ! ( sscanf( s, "%*s %X ", &id ) > 0 ) )
  128. return false;
  129. Loggable *l = find( id );
  130. // assert( l );
  131. char classname[40];
  132. char command[40];
  133. char *arguments;
  134. const char *create, *destroy;
  135. if ( reverse )
  136. {
  137. sscanf( s, "%s %*X %s %*[^\n<]<< %a[^\n]", classname, command, &arguments );
  138. create = "destroy";
  139. destroy = "create";
  140. }
  141. else
  142. {
  143. sscanf( s, "%s %*X %s %a[^\n<]", classname, command, &arguments );
  144. create = "create";
  145. destroy = "destroy";
  146. }
  147. if ( ! strcmp( command, destroy ) )
  148. {
  149. /* deleting eg. a track, which contains a list of other
  150. widgets, causes destroy messages to be emitted for all those
  151. widgets, but when replaying the journal the destroy message
  152. causes the children to be deleted also... This is a temporary
  153. hack. Would it be better to queue up objects for deletion
  154. (when?) */
  155. if ( l )
  156. {
  157. int id = l->id();
  158. delete l;
  159. _loggables[ id ] = NULL;
  160. }
  161. }
  162. else if ( ! strcmp( command, "set" ) )
  163. {
  164. // printf( "got set command (%s).\n", arguments );
  165. char **sa = parse_alist( arguments );
  166. Log_Entry e( sa );
  167. l->log_start();
  168. l->set( e );
  169. l->log_end();
  170. }
  171. else if ( ! strcmp( command, create ) )
  172. {
  173. char **sa = NULL;
  174. if ( arguments )
  175. sa = parse_alist( arguments );
  176. Log_Entry e( sa );
  177. assert( _class_map[ string( classname ) ] );
  178. /* if ( ! _class_map[ string( classname ) ] ) */
  179. /* printf( "error class %s is unregistered!\n", classname ); */
  180. /* else */
  181. {
  182. /* create */
  183. Loggable *l = _class_map[ string( classname ) ]( e );
  184. l->update_id( id );
  185. l->log_create();
  186. }
  187. }
  188. }
  189. void
  190. Loggable::undo ( void )
  191. {
  192. char *buf = new char[ BUFSIZ ];
  193. // fflush( _fp );
  194. fseek( _fp, 0, SEEK_END );
  195. size_t len = ftell( _fp );
  196. fseek( _fp, 0 - (BUFSIZ > len ? len : BUFSIZ), SEEK_END );
  197. len = fread( buf, 1, BUFSIZ, _fp );
  198. char *s = buf + len - 1;
  199. // FIXME: handle blocks
  200. int i = 1;
  201. /* move back _undo_index items from the end */
  202. for ( int j = _undo_index; j-- ; )
  203. for ( --s; *s && s >= buf; --s, ++i )
  204. {
  205. if ( *s == '\n' )
  206. {
  207. if ( *(s + 1) == '\t' )
  208. continue;
  209. if ( *(s + 1) == '}' )
  210. {
  211. *(s + 1) = NULL;
  212. continue;
  213. }
  214. break;
  215. }
  216. }
  217. s++;
  218. buf[ len ] = NULL;
  219. if ( ! strlen( s ) )
  220. {
  221. printf( "corrupt undo file or no undo entries.\n" );
  222. return;
  223. }
  224. char *b = s;
  225. s += strlen( s ) - 1;
  226. if ( strtok( b, "\n" ) == NULL )
  227. {
  228. printf( "error, empty undo transaction!\n" );
  229. abort();
  230. }
  231. int n = 1;
  232. while ( strtok( NULL, "\n" ) )
  233. ++n;
  234. int ui = _undo_index;
  235. block_start();
  236. if ( strcmp( b, "{" ) )
  237. {
  238. /* It's a single undo, get rid of trailing messages in this block */
  239. n = 1;
  240. s = b + 2;
  241. s += strlen( s ) - 1;
  242. }
  243. while ( n-- )
  244. {
  245. while ( s >= b && *(--s) );
  246. s++;
  247. if ( ! strcmp( s, "{" ) )
  248. break;
  249. if ( *s == '\t' )
  250. s++;
  251. printf( "undoing \"%s\"\n", s );
  252. do_this( s, true );
  253. s -= 2;
  254. }
  255. block_end();
  256. // FIXME: bogus... needs to account for multiple events.
  257. _undo_index = ui + 1;
  258. ++_undo_index;
  259. delete buf;
  260. }
  261. /** write a snapshot of the state of all loggable objects, sufficient
  262. * for later reconstruction, to /fp/ */
  263. bool
  264. Loggable::snapshot( FILE *fp )
  265. {
  266. FILE *ofp = _fp;
  267. if ( ! ( _fp = fp ) )
  268. {
  269. _fp = ofp;
  270. return false;
  271. }
  272. for ( int i = 0; i < _log_id; ++i )
  273. {
  274. const Loggable * l = _loggables[ i ];
  275. if ( l && _class_map[ string( l->class_name() ) ] )
  276. l->log_create();
  277. }
  278. _fp = ofp;
  279. return true;
  280. }
  281. void
  282. Loggable::compact ( void )
  283. {
  284. fseek( _fp, 0, SEEK_SET );
  285. ftruncate( fileno( _fp ), 0 );
  286. snapshot( _fp );
  287. _undo_index = 1;
  288. }
  289. void
  290. Loggable::log ( const char *fmt, ... )
  291. {
  292. /* FIXME: bogus limit */
  293. static char buf[1024];
  294. static int i = 0;
  295. va_list args;
  296. if ( fmt )
  297. {
  298. va_start( args, fmt );
  299. i += vsprintf( buf + i, fmt, args );
  300. va_end( args );
  301. }
  302. if ( rindex( buf, '\n' ) )
  303. {
  304. _transaction.push( strdup( buf ) );
  305. i = 0;
  306. }
  307. }
  308. void
  309. Loggable::flush ( void )
  310. {
  311. if ( ! _fp )
  312. {
  313. // printf( "error: no log file open!\n" );
  314. while ( ! _transaction.empty() )
  315. {
  316. free( _transaction.front() );
  317. _transaction.pop();
  318. }
  319. return;
  320. }
  321. int n = _transaction.size();
  322. if ( n > 1 )
  323. fprintf( _fp, "{\n" );
  324. while ( ! _transaction.empty() )
  325. {
  326. char *s = _transaction.front();
  327. _transaction.pop();
  328. if ( n > 1 )
  329. fprintf( _fp, "\t" );
  330. fprintf( _fp, "%s", s );
  331. free( s );
  332. }
  333. if ( n > 1 )
  334. fprintf( _fp, "}\n" );
  335. fflush( _fp );
  336. }
  337. void
  338. Loggable::log_print( char **o, char **n ) const
  339. {
  340. if ( n )
  341. for ( ; *n; n++ )
  342. log( "%s %s%s", *n, *n + strlen( *n ) + 1, *(n + 1) ? " " : "" );
  343. if ( o && *o )
  344. {
  345. if ( n ) log( " << " );
  346. for ( ; *o; o++ )
  347. log( "%s %s%s", *o, *o + strlen( *o ) + 1, *(o + 1) ? " " : "" );
  348. }
  349. log( "\n" );
  350. }
  351. /** compare elements of dumps s1 and s2, removing those elements
  352. of dst which are not changed from src */
  353. static
  354. bool
  355. log_diff ( char **sa1, char **sa2 )
  356. {
  357. if ( ! sa1 )
  358. return true;
  359. int w = 0;
  360. for ( int i = 0; sa1[ i ]; ++i )
  361. {
  362. const char *v1 = sa1[ i ] + strlen( sa1[ i ] ) + 1;
  363. const char *v2 = sa2[ i ] + strlen( sa2[ i ] ) + 1;
  364. if ( ! strcmp( sa1[ i ], sa2[ i ] ) && ! strcmp( v1, v2 ) )
  365. {
  366. free( sa2[ i ] );
  367. free( sa1[ i ] );
  368. }
  369. else
  370. {
  371. sa2[ w ] = sa2[ i ];
  372. sa1[ w ] = sa1[ i ];
  373. w++;
  374. }
  375. }
  376. sa1[ w ] = NULL;
  377. sa2[ w ] = NULL;
  378. return w == 0 ? false : true;
  379. }
  380. void
  381. Loggable::log_start ( void )
  382. {
  383. if ( ! _old_state )
  384. {
  385. Log_Entry e;
  386. get( e );
  387. _old_state = e.sa();
  388. }
  389. ++_nest;
  390. _undo_index = 1;
  391. }
  392. void
  393. Loggable::log_end ( void )
  394. {
  395. if ( --_nest > 0 )
  396. return;
  397. char **_new_state;
  398. {
  399. Log_Entry e;
  400. get( e );
  401. _new_state = e.sa();
  402. }
  403. if ( log_diff( _old_state, _new_state ) )
  404. {
  405. // indent();
  406. log( "%s 0x%X set ", class_name(), _id );
  407. log_print( _old_state, _new_state );
  408. }
  409. if ( _new_state )
  410. free_sa( _new_state );
  411. if ( _old_state )
  412. free_sa( _old_state );
  413. _old_state = NULL;
  414. if ( Loggable::_level == 0 )
  415. Loggable::flush();
  416. }
  417. void
  418. Loggable::log_create ( void ) const
  419. {
  420. // indent();
  421. /* if ( Loggable::_level ) */
  422. /* { */
  423. /* /\* defer until the block ends--this is really only required for capturing... *\/ */
  424. /* } */
  425. log( "%s 0x%X create ", class_name(), _id );
  426. char **sa;
  427. Log_Entry e;
  428. get( e );
  429. sa = e.sa();
  430. if ( sa )
  431. {
  432. log_print( NULL, sa );
  433. free_sa( sa );
  434. }
  435. else
  436. log( "\n" );
  437. if ( Loggable::_level == 0 )
  438. Loggable::flush();
  439. }
  440. void
  441. Loggable::log_destroy ( void ) const
  442. {
  443. // indent();
  444. log( "%s 0x%X destroy (nothing) << ", class_name(), _id );
  445. char **sa;
  446. Log_Entry e;
  447. get( e );
  448. sa = e.sa();
  449. // log_print( sa, NULL );
  450. log_print( NULL, sa );
  451. free_sa( sa );
  452. if ( Loggable::_level == 0 )
  453. Loggable::flush();
  454. }