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.

572 lines
11KB

  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. strncpy( pair, c, l );
  85. pair[ l ] = '\0';
  86. r[ i++ ] = pair;
  87. /* split */
  88. strtok( pair, " " );
  89. /* remove quotes */
  90. char *v = pair + strlen( pair ) + 1;
  91. if ( *v == '"' )
  92. {
  93. // v++;
  94. v[ strlen( v ) - 2 ] = '\0';
  95. memmove( v, v + 1, strlen( v ) + 1 );
  96. }
  97. }
  98. c = s;
  99. if ( *s == '\0' )
  100. break;
  101. }
  102. }
  103. r[ i ] = NULL;
  104. return r;
  105. }
  106. static
  107. void free_sa ( char **sa )
  108. {
  109. char **a = sa;
  110. for ( ; *a; a++ )
  111. free( *a );
  112. free( sa );
  113. }
  114. /** 'do' a message like "Region 0xF1 set :r 123" */
  115. bool
  116. Loggable::do_this ( const char *s, bool reverse )
  117. {
  118. int id;
  119. if ( ! ( sscanf( s, "%*s %X ", &id ) > 0 ) )
  120. return false;
  121. Loggable *l = find( id );
  122. // assert( l );
  123. char classname[40];
  124. char command[40];
  125. char *arguments;
  126. const char *create, *destroy;
  127. if ( reverse )
  128. {
  129. sscanf( s, "%s %*X %s %*[^\n<]<< %a[^\n]", classname, command, &arguments );
  130. create = "destroy";
  131. destroy = "create";
  132. }
  133. else
  134. {
  135. sscanf( s, "%s %*X %s %a[^\n<]", classname, command, &arguments );
  136. create = "create";
  137. destroy = "destroy";
  138. }
  139. if ( ! strcmp( command, destroy ) )
  140. {
  141. int id = l->id();
  142. delete l;
  143. _loggables[ id ] = NULL;
  144. }
  145. else if ( ! strcmp( command, "set" ) )
  146. {
  147. printf( "got set command (%s).\n", arguments );
  148. char **sa = parse_alist( arguments );
  149. Log_Entry e( sa );
  150. l->log_start();
  151. l->set( e );
  152. l->log_end();
  153. }
  154. else if ( ! strcmp( command, create ) )
  155. {
  156. char **sa = NULL;
  157. if ( arguments )
  158. sa = parse_alist( arguments );
  159. Log_Entry e( sa );
  160. assert( _class_map[ string( classname ) ] );
  161. /* if ( ! _class_map[ string( classname ) ] ) */
  162. /* printf( "error class %s is unregistered!\n", classname ); */
  163. /* else */
  164. {
  165. /* create */
  166. Loggable *l = _class_map[ string( classname ) ]( e );
  167. l->update_id( id );
  168. l->log_create();
  169. }
  170. }
  171. }
  172. void
  173. Loggable::undo ( void )
  174. {
  175. char *buf = new char[ BUFSIZ ];
  176. // fflush( _fp );
  177. fseek( _fp, 0, SEEK_END );
  178. size_t len = ftell( _fp );
  179. fseek( _fp, 0 - (BUFSIZ > len ? len : BUFSIZ), SEEK_END );
  180. len = fread( buf, 1, BUFSIZ, _fp );
  181. char *s = buf + len - 1;
  182. // FIXME: handle blocks
  183. int i = 1;
  184. /* move back _undo_index items from the end */
  185. for ( int j = _undo_index; j-- ; )
  186. for ( --s; *s && s >= buf; --s, ++i )
  187. {
  188. if ( *s == '\n' )
  189. {
  190. if ( *(s + 1) == '\t' )
  191. continue;
  192. if ( *(s + 1) == '}' )
  193. {
  194. *(s + 1) = NULL;
  195. continue;
  196. }
  197. break;
  198. }
  199. }
  200. s++;
  201. buf[ len ] = NULL;
  202. if ( ! strlen( s ) )
  203. {
  204. printf( "corrupt undo file or no undo entries.\n" );
  205. return;
  206. }
  207. char *b = s;
  208. s += strlen( s ) - 1;
  209. if ( strtok( b, "\n" ) == NULL )
  210. {
  211. printf( "error, empty undo transaction!\n" );
  212. abort();
  213. }
  214. int n = 1;
  215. while ( strtok( NULL, "\n" ) )
  216. ++n;
  217. int ui = _undo_index;
  218. block_start();
  219. if ( strcmp( b, "{" ) )
  220. {
  221. /* It's a single undo, get rid of trailing messages in this block */
  222. n = 1;
  223. s = b + 2;
  224. s += strlen( s ) - 1;
  225. }
  226. while ( n-- )
  227. {
  228. while ( s >= b && *(--s) );
  229. s++;
  230. if ( ! strcmp( s, "{" ) )
  231. break;
  232. if ( *s == '\t' )
  233. s++;
  234. printf( "undoing \"%s\"\n", s );
  235. do_this( s, true );
  236. s -= 2;
  237. }
  238. block_end();
  239. // FIXME: bogus... needs to account for multiple events.
  240. _undo_index = ui + 1;
  241. ++_undo_index;
  242. delete buf;
  243. }
  244. /** write a snapshot of the state of all loggable objects, sufficient
  245. * for later reconstruction, to /file/ */
  246. bool
  247. Loggable::snapshot( const char *file )
  248. {
  249. FILE *ofp = _fp;
  250. if ( ! ( _fp = fopen( file, "a" ) ) )
  251. {
  252. _fp = ofp;
  253. return false;
  254. }
  255. for ( int i = 0; i < _log_id; ++i )
  256. {
  257. if ( _class_map[ string( _loggables[ i ]->class_name() ) ] )
  258. _loggables[ i ]->log_create();
  259. }
  260. fclose( _fp );
  261. _fp = ofp;
  262. return true;
  263. }
  264. void
  265. Loggable::log ( const char *fmt, ... )
  266. {
  267. /* FIXME: bogus limit */
  268. static char buf[1024];
  269. static int i = 0;
  270. va_list args;
  271. if ( fmt )
  272. {
  273. va_start( args, fmt );
  274. i += vsprintf( buf + i, fmt, args );
  275. va_end( args );
  276. }
  277. if ( rindex( buf, '\n' ) )
  278. {
  279. _transaction.push( strdup( buf ) );
  280. i = 0;
  281. }
  282. }
  283. void
  284. Loggable::flush ( void )
  285. {
  286. if ( ! _fp )
  287. {
  288. // printf( "error: no log file open!\n" );
  289. while ( ! _transaction.empty() )
  290. {
  291. free( _transaction.front() );
  292. _transaction.pop();
  293. }
  294. return;
  295. }
  296. int n = _transaction.size();
  297. if ( n > 1 )
  298. fprintf( _fp, "{\n" );
  299. while ( ! _transaction.empty() )
  300. {
  301. char *s = _transaction.front();
  302. _transaction.pop();
  303. if ( n > 1 )
  304. fprintf( _fp, "\t" );
  305. fprintf( _fp, "%s", s );
  306. free( s );
  307. }
  308. if ( n > 1 )
  309. fprintf( _fp, "}\n" );
  310. fflush( _fp );
  311. }
  312. void
  313. Loggable::log_print( char **o, char **n )
  314. {
  315. if ( n )
  316. for ( ; *n; n++ )
  317. log( "%s %s%s", *n, *n + strlen( *n ) + 1, *(n + 1) ? " " : "" );
  318. if ( o && *o )
  319. {
  320. if ( n ) log( " << " );
  321. for ( ; *o; o++ )
  322. log( "%s %s%s", *o, *o + strlen( *o ) + 1, *(o + 1) ? " " : "" );
  323. }
  324. log( "\n" );
  325. }
  326. /** compare elements of dumps s1 and s2, removing those elements
  327. of dst which are not changed from src */
  328. static
  329. bool
  330. log_diff ( char **sa1, char **sa2 )
  331. {
  332. if ( ! sa1 )
  333. return true;
  334. int w = 0;
  335. for ( int i = 0; sa1[ i ]; ++i )
  336. {
  337. const char *v1 = sa1[ i ] + strlen( sa1[ i ] ) + 1;
  338. const char *v2 = sa2[ i ] + strlen( sa2[ i ] ) + 1;
  339. if ( ! strcmp( sa1[ i ], sa2[ i ] ) && ! strcmp( v1, v2 ) )
  340. {
  341. free( sa2[ i ] );
  342. free( sa1[ i ] );
  343. }
  344. else
  345. {
  346. sa2[ w ] = sa2[ i ];
  347. sa1[ w ] = sa1[ i ];
  348. w++;
  349. }
  350. }
  351. sa1[ w ] = NULL;
  352. sa2[ w ] = NULL;
  353. return w == 0 ? false : true;
  354. }
  355. void
  356. Loggable::log_start ( void )
  357. {
  358. if ( ! _old_state )
  359. {
  360. Log_Entry e;
  361. get( e );
  362. _old_state = e.sa();
  363. }
  364. ++_nest;
  365. _undo_index = 1;
  366. }
  367. void
  368. Loggable::log_end ( void )
  369. {
  370. if ( --_nest > 0 )
  371. return;
  372. char **_new_state;
  373. {
  374. Log_Entry e;
  375. get( e );
  376. _new_state = e.sa();
  377. }
  378. if ( log_diff( _old_state, _new_state ) )
  379. {
  380. // indent();
  381. log( "%s 0x%X set ", class_name(), _id );
  382. log_print( _old_state, _new_state );
  383. }
  384. if ( _new_state )
  385. free_sa( _new_state );
  386. if ( _old_state )
  387. free_sa( _old_state );
  388. _old_state = NULL;
  389. if ( Loggable::_level == 0 )
  390. Loggable::flush();
  391. }
  392. void
  393. Loggable::log_create ( void )
  394. {
  395. // indent();
  396. log( "%s 0x%X create ", class_name(), _id );
  397. char **sa;
  398. Log_Entry e;
  399. get( e );
  400. sa = e.sa();
  401. if ( sa )
  402. {
  403. log_print( NULL, sa );
  404. free_sa( sa );
  405. }
  406. else
  407. log( "\n" );
  408. if ( Loggable::_level == 0 )
  409. Loggable::flush();
  410. }
  411. void
  412. Loggable::log_destroy ( void )
  413. {
  414. // indent();
  415. log( "%s 0x%X destroy (nothing) << ", class_name(), _id );
  416. char **sa;
  417. Log_Entry e;
  418. get( e );
  419. sa = e.sa();
  420. // log_print( sa, NULL );
  421. log_print( NULL, sa );
  422. free_sa( sa );
  423. if ( Loggable::_level == 0 )
  424. Loggable::flush();
  425. }