2345 lines
56 KiB
C
2345 lines
56 KiB
C
|
||
/*******************************************************************************/
|
||
/* Copyright (C) 2010 Jonathan Moore Liles */
|
||
/* */
|
||
/* This program is free software; you can redistribute it and/or modify it */
|
||
/* under the terms of the GNU General Public License as published by the */
|
||
/* Free Software Foundation; either version 2 of the License, or (at your */
|
||
/* option) any later version. */
|
||
/* */
|
||
/* This program is distributed in the hope that it will be useful, but WITHOUT */
|
||
/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */
|
||
/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for */
|
||
/* more details. */
|
||
/* */
|
||
/* You should have received a copy of the GNU General Public License along */
|
||
/* with This program; see the file COPYING. If not,write to the Free Software */
|
||
/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
|
||
/*******************************************************************************/
|
||
|
||
#define __MODULE__ "nsmd"
|
||
|
||
#include "debug.h"
|
||
|
||
#ifndef _GNU_SOURCE
|
||
#define _GNU_SOURCE
|
||
#endif
|
||
|
||
#include <errno.h>
|
||
#include <string.h>
|
||
#include <list>
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <unistd.h>
|
||
#include <sys/types.h>
|
||
#include <signal.h>
|
||
#include <sys/signalfd.h>
|
||
#include <sys/stat.h>
|
||
#include <sys/wait.h>
|
||
#include <unistd.h>
|
||
#include <time.h>
|
||
#include <libgen.h>
|
||
#include <dirent.h>
|
||
#include <ftw.h>
|
||
#include <list>
|
||
#include <getopt.h>
|
||
#include <sys/time.h>
|
||
|
||
#include <OSC/Endpoint.H>
|
||
/* for locking */
|
||
#include "file.h"
|
||
|
||
#include <map>
|
||
#include <string>
|
||
#include <algorithm>
|
||
|
||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||
|
||
static OSC::Endpoint *osc_server;
|
||
static lo_address gui_addr;
|
||
static bool gui_is_active = false;
|
||
static int signal_fd;
|
||
|
||
static int session_lock_fd = 0;
|
||
static char *session_root;
|
||
|
||
#define NSM_API_VERSION_MAJOR 1
|
||
#define NSM_API_VERSION_MINOR 0
|
||
|
||
#define ERR_OK 0
|
||
#define ERR_GENERAL_ERROR -1
|
||
#define ERR_INCOMPATIBLE_API -2
|
||
#define ERR_BLACKLISTED -3
|
||
#define ERR_LAUNCH_FAILED -4
|
||
#define ERR_NO_SUCH_FILE -5
|
||
#define ERR_NO_SESSION_OPEN -6
|
||
#define ERR_UNSAVED_CHANGES -7
|
||
#define ERR_NOT_NOW -8
|
||
#define ERR_BAD_PROJECT -9
|
||
#define ERR_CREATE_FAILED -10
|
||
#define ERR_SESSION_LOCKED -11
|
||
#define ERR_OPERATION_PENDING -12
|
||
|
||
#define APP_TITLE "Non Session Manager"
|
||
|
||
enum {
|
||
COMMAND_NONE = 0,
|
||
COMMAND_QUIT,
|
||
COMMAND_KILL,
|
||
COMMAND_SAVE,
|
||
COMMAND_OPEN,
|
||
COMMAND_START,
|
||
|
||
COMMAND_CLOSE,
|
||
COMMAND_DUPLICATE,
|
||
COMMAND_NEW
|
||
};
|
||
|
||
static int pending_operation = COMMAND_NONE;
|
||
|
||
static void wait ( long );
|
||
|
||
|
||
#define GUIMSG( fmt, args... ) \
|
||
{ \
|
||
MESSAGE( fmt, ## args ); \
|
||
if ( gui_is_active ) \
|
||
{ \
|
||
char *s;\
|
||
asprintf( &s, fmt, ## args );\
|
||
osc_server->send( gui_addr, "/nsm/gui/server/message", s);\
|
||
free(s);\
|
||
}\
|
||
}
|
||
|
||
struct Client
|
||
{
|
||
private:
|
||
|
||
int _reply_errcode;
|
||
char *_reply_message;
|
||
|
||
int _pending_command; /* */
|
||
struct timeval _command_sent_time;
|
||
|
||
bool _gui_visible;
|
||
|
||
char *_label;
|
||
|
||
public:
|
||
|
||
lo_address addr; /* */
|
||
char *name; /* client application name */
|
||
char *executable_path; /* path to client executable */
|
||
int pid; /* PID of client process */
|
||
float progress; /* */
|
||
bool active; /* client has registered via announce */
|
||
// bool stopped; /* the client quit, but not because we told it to--user still has to decide to remove it from the session */
|
||
char *client_id; /* short part of client ID */
|
||
char *capabilities; /* client capabilities... will be null for dumb clients */
|
||
bool dirty; /* flag for client self-reported dirtiness */
|
||
bool pre_existing;
|
||
const char *status;
|
||
|
||
const char *label ( void ) const { return _label; }
|
||
void label ( const char *l )
|
||
{
|
||
if ( _label )
|
||
free( _label );
|
||
if ( l )
|
||
_label = strdup( l );
|
||
else
|
||
_label = NULL;
|
||
}
|
||
|
||
bool gui_visible ( void ) const
|
||
{
|
||
return _gui_visible;
|
||
}
|
||
|
||
void gui_visible ( bool b )
|
||
{
|
||
_gui_visible = b;
|
||
}
|
||
|
||
bool
|
||
has_error ( void ) const
|
||
{
|
||
return _reply_errcode != 0;
|
||
}
|
||
|
||
int
|
||
error_code ( void ) const
|
||
{
|
||
return _reply_errcode;
|
||
}
|
||
|
||
const char * message ( void )
|
||
{
|
||
return _reply_message;
|
||
}
|
||
|
||
void
|
||
set_reply ( int errcode, const char *message )
|
||
{
|
||
if ( _reply_message )
|
||
free( _reply_message );
|
||
|
||
_reply_message = strdup( message );
|
||
_reply_errcode = errcode;
|
||
}
|
||
|
||
bool reply_pending ( void )
|
||
{
|
||
return _pending_command != COMMAND_NONE;
|
||
}
|
||
|
||
bool is_dumb_client ( void )
|
||
{
|
||
return capabilities == NULL;
|
||
}
|
||
|
||
void pending_command ( int command )
|
||
{
|
||
gettimeofday( &_command_sent_time, NULL );
|
||
_pending_command = command;
|
||
}
|
||
|
||
double milliseconds_since_last_command ( void ) const
|
||
{
|
||
struct timeval now;
|
||
|
||
gettimeofday( &now, NULL );
|
||
|
||
double elapsedms = ( now.tv_sec - _command_sent_time.tv_sec ) * 1000.0;
|
||
elapsedms += ( now.tv_usec - _command_sent_time.tv_usec ) / 1000.0;
|
||
|
||
return elapsedms;
|
||
}
|
||
|
||
int pending_command ( void )
|
||
{
|
||
return _pending_command;
|
||
}
|
||
|
||
// capability should be enclosed in colons. I.e. ":switch:"
|
||
bool
|
||
is_capable_of ( const char *capability ) const
|
||
{
|
||
return capabilities &&
|
||
strstr( capabilities, capability );
|
||
}
|
||
|
||
Client ( )
|
||
{
|
||
_label = 0;
|
||
_gui_visible = true;
|
||
addr = 0;
|
||
_reply_errcode = 0;
|
||
_reply_message = 0;
|
||
pid = 0;
|
||
progress = -0;
|
||
_pending_command = 0;
|
||
active = false;
|
||
client_id = 0;
|
||
capabilities = 0;
|
||
name = 0;
|
||
executable_path = 0;
|
||
pre_existing = false;
|
||
}
|
||
|
||
~Client ( )
|
||
{
|
||
if ( name )
|
||
free(name);
|
||
if (executable_path)
|
||
free(executable_path);
|
||
if (client_id)
|
||
free(client_id);
|
||
if (capabilities)
|
||
free(capabilities);
|
||
|
||
name = executable_path = client_id = capabilities = NULL;
|
||
}
|
||
};
|
||
|
||
|
||
static std::list< Client* > client;
|
||
|
||
/* helper macros for defining OSC handlers */
|
||
#define OSC_NAME( name ) osc_ ## name
|
||
// #define OSCDMSG() DMESSAGE( "Got OSC message: %s", path );
|
||
#define OSC_HANDLER( name ) static int OSC_NAME( name ) ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
|
||
|
||
static char *session_path = NULL;
|
||
static char *session_name = NULL;
|
||
|
||
bool
|
||
clients_have_errors ( )
|
||
{
|
||
for ( std::list<Client*>::const_iterator i = client.begin();
|
||
i != client.end();
|
||
++i )
|
||
if ( (*i)->active && (*i)->has_error() )
|
||
return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
Client *
|
||
get_client_by_pid ( int pid )
|
||
{
|
||
std::list<Client*> *cl = &client;
|
||
|
||
for ( std::list<Client*>::const_iterator i = cl->begin();
|
||
i != cl->end();
|
||
++i )
|
||
if ( (*i)->pid == pid )
|
||
return *i;
|
||
|
||
return NULL;
|
||
}
|
||
|
||
void clear_clients ( void )
|
||
{
|
||
std::list<Client*> *cl = &client;
|
||
|
||
for ( std::list<Client*>::iterator i = cl->begin();
|
||
i != cl->end();
|
||
++i )
|
||
{
|
||
delete *i;
|
||
i = cl->erase( i );
|
||
}
|
||
}
|
||
|
||
|
||
void
|
||
handle_client_process_death ( int pid )
|
||
{
|
||
Client *c = get_client_by_pid( (int)pid );
|
||
|
||
if ( c )
|
||
{
|
||
bool dead_because_we_said = ( c->pending_command() == COMMAND_KILL ||
|
||
c->pending_command() == COMMAND_QUIT );
|
||
|
||
if ( dead_because_we_said )
|
||
{
|
||
GUIMSG( "Client %s terminated because we told it to.", c->name );
|
||
}
|
||
else
|
||
{
|
||
GUIMSG( "Client %s died unexpectedly.", c->name );
|
||
}
|
||
|
||
if ( c->pending_command() == COMMAND_QUIT )
|
||
{
|
||
if ( gui_is_active )
|
||
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "removed" );
|
||
|
||
client.remove(c);
|
||
delete c;
|
||
}
|
||
else
|
||
{
|
||
if ( gui_is_active )
|
||
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "stopped" );
|
||
}
|
||
|
||
c->pending_command( COMMAND_NONE );
|
||
|
||
c->active = false;
|
||
c->pid = 0;
|
||
}
|
||
}
|
||
|
||
void handle_sigchld ( )
|
||
{
|
||
for ( ;; )
|
||
{
|
||
int status;
|
||
pid_t pid = waitpid(-1, &status, WNOHANG);
|
||
|
||
if (pid <= 0)
|
||
break;
|
||
|
||
handle_client_process_death( pid );
|
||
}
|
||
}
|
||
|
||
|
||
int
|
||
path_is_valid ( const char *path )
|
||
{
|
||
char *s;
|
||
|
||
asprintf( &s, "/%s/", path );
|
||
|
||
int r = strstr( s, "/../" ) == NULL;
|
||
|
||
free( s );
|
||
|
||
return r;
|
||
}
|
||
|
||
int
|
||
mkpath ( const char *path, bool create_final_directory )
|
||
{
|
||
char *p = strdup( path );
|
||
|
||
char *i = p + 1;
|
||
|
||
while ( ( i = index( i, '/' ) ) )
|
||
{
|
||
*i = 0;
|
||
|
||
struct stat st;
|
||
|
||
if ( stat( p, &st ) )
|
||
{
|
||
if ( mkdir( p, 0711 ) )
|
||
{
|
||
free( p );
|
||
return -1;
|
||
}
|
||
}
|
||
|
||
*i = '/';
|
||
i++;
|
||
}
|
||
|
||
if ( create_final_directory )
|
||
{
|
||
if ( mkdir( p, 0711 ) )
|
||
{
|
||
free( p );
|
||
return -1;
|
||
}
|
||
}
|
||
|
||
free( p );
|
||
|
||
return 0;
|
||
}
|
||
|
||
void
|
||
set_name ( const char *name )
|
||
{
|
||
if ( session_name )
|
||
free( session_name );
|
||
|
||
char *s = strdup( name );
|
||
|
||
session_name = strdup( basename( s ) );
|
||
|
||
free( s );
|
||
}
|
||
|
||
bool
|
||
address_matches ( lo_address addr1, lo_address addr2 )
|
||
{
|
||
/* char *url1 = lo_address_get_url( addr1 ); */
|
||
/* char *url2 = lo_address_get_url( addr2 ); */
|
||
|
||
char *url1 = strdup( lo_address_get_port( addr1 ) );
|
||
char *url2 = strdup(lo_address_get_port( addr2 ) );
|
||
|
||
bool r = !strcmp( url1, url2 );
|
||
|
||
free( url1 );
|
||
free( url2 );
|
||
|
||
return r;
|
||
}
|
||
|
||
Client *
|
||
get_client_by_id ( std::list<Client*> *cl, const char *id )
|
||
{
|
||
for ( std::list<Client*>::const_iterator i = cl->begin();
|
||
i != cl->end();
|
||
++i )
|
||
if ( !strcmp( (*i)->client_id, id ) )
|
||
return *i;
|
||
|
||
return NULL;
|
||
}
|
||
|
||
|
||
Client *
|
||
get_client_by_name_and_id ( std::list<Client*> *cl, const char *name, const char *id )
|
||
{
|
||
for ( std::list<Client*>::const_iterator i = cl->begin();
|
||
i != cl->end();
|
||
++i )
|
||
if ( !strcmp( (*i)->client_id, id ) &&
|
||
! strcmp( (*i)->name, name ) )
|
||
return *i;
|
||
|
||
return NULL;
|
||
}
|
||
|
||
Client *
|
||
get_client_by_address ( lo_address addr )
|
||
{
|
||
for ( std::list<Client*>::iterator i = client.begin();
|
||
i != client.end();
|
||
++i )
|
||
if ( (*i)->addr && address_matches( (*i)->addr, addr ) )
|
||
return *i;
|
||
|
||
return NULL;
|
||
}
|
||
|
||
|
||
char *
|
||
generate_client_id ( Client *c )
|
||
{
|
||
char id_str[6];
|
||
|
||
id_str[0] = 'n';
|
||
id_str[5] = 0;
|
||
|
||
for ( int i = 1; i < 5; i++)
|
||
id_str[i] = 'A' + (rand() % 25);
|
||
|
||
return strdup(id_str);
|
||
}
|
||
|
||
|
||
bool
|
||
replies_still_pending ( void )
|
||
{
|
||
for ( std::list<Client*>::const_iterator i = client.begin();
|
||
i != client.end();
|
||
++i )
|
||
if ( (*i)->active && (*i)->reply_pending() )
|
||
return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
int
|
||
number_of_active_clients ( void )
|
||
{
|
||
int active = 0;
|
||
for ( std::list<Client*>::const_iterator i = client.begin(); i != client.end(); i++ )
|
||
{
|
||
if ( (*i)->active )
|
||
active++;
|
||
}
|
||
|
||
return active;
|
||
}
|
||
|
||
void
|
||
wait_for_announce ( void )
|
||
{
|
||
GUIMSG( "Waiting for announce messages from clients" );
|
||
|
||
int n = 5 * 1000;
|
||
|
||
long unsigned int active;
|
||
|
||
while ( n > 0 )
|
||
{
|
||
n -= 100;
|
||
|
||
wait(100);
|
||
|
||
active = number_of_active_clients();
|
||
|
||
if ( client.size() == active )
|
||
break;
|
||
}
|
||
|
||
GUIMSG( "Done. %lu out of %lu clients announced within the initialization grace period", active, client.size() );
|
||
}
|
||
|
||
void
|
||
wait_for_replies ( void )
|
||
{
|
||
|
||
GUIMSG( "Waiting for clients to reply to commands" );
|
||
|
||
int n = 60 * 1000; /* 60 seconds */
|
||
|
||
while ( n )
|
||
{
|
||
n -= 100;
|
||
|
||
wait(100);
|
||
|
||
if ( ! replies_still_pending() )
|
||
break;
|
||
}
|
||
|
||
GUIMSG( "Done waiting" );
|
||
/* FIXME: do something about unresponsive clients */
|
||
}
|
||
|
||
|
||
char *
|
||
get_client_project_path ( const char *session_path, Client *c )
|
||
{
|
||
char *client_project_path;
|
||
|
||
asprintf( &client_project_path, "%s/%s.%s", session_path, c->name, c->client_id );
|
||
|
||
return client_project_path;
|
||
}
|
||
|
||
bool
|
||
launch ( const char *executable, const char *client_id )
|
||
{
|
||
Client *c;
|
||
|
||
if ( !client_id || !( c = get_client_by_id( &client, client_id ) ) )
|
||
{
|
||
c = new Client();
|
||
|
||
c->executable_path = strdup( executable );
|
||
|
||
{
|
||
char *s = strdup( c->executable_path );
|
||
|
||
c->name = strdup( basename( s ) );
|
||
|
||
free( s );
|
||
}
|
||
|
||
if ( client_id )
|
||
c->client_id = strdup( client_id );
|
||
else
|
||
c->client_id = generate_client_id( c );
|
||
|
||
client.push_back( c );
|
||
}
|
||
|
||
char * url = osc_server->url();
|
||
|
||
int pid;
|
||
if ( ! (pid = fork()) )
|
||
{
|
||
GUIMSG( "Launching %s", executable );
|
||
|
||
char *args[] = { strdup( executable ), NULL };
|
||
|
||
setenv( "NSM_URL", url, 1 );
|
||
|
||
if ( -1 == execvp( executable, args ) )
|
||
{
|
||
WARNING( "Error starting process: %s", strerror( errno ) );
|
||
|
||
exit(-1);
|
||
}
|
||
}
|
||
|
||
c->pending_command( COMMAND_START );
|
||
c->pid = pid;
|
||
|
||
MESSAGE( "Process has pid: %i", pid );
|
||
|
||
if ( gui_is_active )
|
||
{
|
||
osc_server->send( gui_addr, "/nsm/gui/client/new", c->client_id, c->name );
|
||
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "launch" );
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
void
|
||
command_client_to_save ( Client *c )
|
||
{
|
||
if ( c->active )
|
||
{
|
||
MESSAGE( "Telling %s to save", c->name );
|
||
osc_server->send( c->addr, "/nsm/client/save" );
|
||
|
||
c->pending_command( COMMAND_SAVE );
|
||
|
||
if ( gui_is_active )
|
||
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "save" );
|
||
}
|
||
else if ( c->is_dumb_client() && c->pid )
|
||
{
|
||
// this is a dumb client...
|
||
if ( gui_is_active )
|
||
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "noop" );
|
||
}
|
||
}
|
||
|
||
void command_client_to_switch ( Client *c, const char *new_client_id )
|
||
{
|
||
char *old_client_id = c->client_id;
|
||
|
||
c->client_id = strdup( new_client_id );
|
||
|
||
char *client_project_path = get_client_project_path( session_path, c );
|
||
|
||
MESSAGE( "Commanding %s to switch \"%s\"", c->name, client_project_path );
|
||
|
||
char *full_client_id;
|
||
asprintf( &full_client_id, "%s.%s", c->name, c->client_id );
|
||
|
||
osc_server->send( c->addr, "/nsm/client/open", client_project_path, session_name, full_client_id );
|
||
|
||
free( full_client_id );
|
||
free( client_project_path );
|
||
|
||
c->pending_command( COMMAND_OPEN );
|
||
|
||
if ( gui_is_active )
|
||
{
|
||
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "switch" );
|
||
osc_server->send( gui_addr, "/nsm/gui/client/switch", old_client_id, c->client_id );
|
||
}
|
||
|
||
free( old_client_id );
|
||
}
|
||
|
||
void
|
||
purge_inactive_clients ( )
|
||
{
|
||
for ( std::list<Client*>::iterator i = client.begin();
|
||
i != client.end();
|
||
++i )
|
||
{
|
||
if ( ! (*i)->active )
|
||
{
|
||
if ( gui_is_active )
|
||
osc_server->send( gui_addr, "/nsm/gui/client/status", (*i)->client_id, (*i)->status = "removed" );
|
||
|
||
delete *i;
|
||
|
||
i = client.erase( i );
|
||
}
|
||
}
|
||
}
|
||
|
||
bool
|
||
process_is_running ( int pid )
|
||
{
|
||
if ( 0 == kill( pid, 0 ) )
|
||
{
|
||
return true;
|
||
}
|
||
else if ( ESRCH == errno )
|
||
{
|
||
return false;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
void
|
||
purge_dead_clients ( )
|
||
{
|
||
std::list<Client*> tmp( client );
|
||
|
||
for ( std::list<Client*>::const_iterator i = tmp.begin();
|
||
i != tmp.end();
|
||
++i )
|
||
{
|
||
const Client *c = *i;
|
||
if ( c->pid )
|
||
{
|
||
if ( ! process_is_running( c->pid ) )
|
||
handle_client_process_death( c->pid );
|
||
}
|
||
}
|
||
}
|
||
|
||
/************************/
|
||
/* OSC Message Handlers */
|
||
/************************/
|
||
|
||
OSC_HANDLER( add )
|
||
{
|
||
if ( ! session_path )
|
||
{
|
||
osc_server->send( lo_message_get_source( msg ), "/error", path,
|
||
ERR_NO_SESSION_OPEN,
|
||
"Cannot add to session because no session is loaded." );
|
||
|
||
|
||
return 0;
|
||
}
|
||
|
||
if ( strchr( &argv[0]->s, '/' ) )
|
||
{
|
||
osc_server->send( lo_message_get_source( msg ), "/error", path,
|
||
ERR_LAUNCH_FAILED,
|
||
"Absolute paths are not permitted. Clients must be in $PATH" );
|
||
return 0;
|
||
}
|
||
|
||
if ( ! launch( &argv[0]->s, NULL ) )
|
||
{
|
||
osc_server->send( lo_message_get_source( msg ), "/error", path,
|
||
ERR_LAUNCH_FAILED,
|
||
"Failed to launch process!" );
|
||
}
|
||
else
|
||
{
|
||
osc_server->send( lo_message_get_source( msg ), "/reply", path,
|
||
ERR_OK,
|
||
"Launched." );
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
OSC_HANDLER( announce )
|
||
{
|
||
const char *client_name = &argv[0]->s;
|
||
const char *capabilities = &argv[1]->s;
|
||
const char *executable_path = &argv[2]->s;
|
||
int major = argv[3]->i;
|
||
int minor = argv[4]->i;
|
||
int pid = argv[5]->i;
|
||
|
||
GUIMSG( "Got announce from %s", client_name );
|
||
|
||
if ( ! session_path )
|
||
{
|
||
osc_server->send( lo_message_get_source( msg ), "/error",
|
||
path,
|
||
ERR_NO_SESSION_OPEN,
|
||
"Sorry, but there's no session open for this application to join." );
|
||
return 0;
|
||
}
|
||
|
||
bool expected_client = false;
|
||
|
||
Client *c = NULL;
|
||
|
||
for ( std::list<Client*>::iterator i = client.begin();
|
||
i != client.end();
|
||
++i )
|
||
{
|
||
if ( ! strcmp( (*i)->executable_path, executable_path ) &&
|
||
! (*i)->active &&
|
||
(*i)->pending_command() == COMMAND_START )
|
||
{
|
||
// I think we've found the slot we were looking for.
|
||
MESSAGE( "Client was expected." );
|
||
c = *i;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if ( ! c )
|
||
{
|
||
c = new Client();
|
||
c->executable_path = strdup( executable_path );
|
||
c->client_id = generate_client_id( c );
|
||
}
|
||
else
|
||
expected_client = true;
|
||
|
||
if ( major > NSM_API_VERSION_MAJOR )
|
||
{
|
||
MESSAGE( "Client is using incompatible and more recent API version %i.%i", major, minor );
|
||
|
||
osc_server->send( lo_message_get_source( msg ), "/error",
|
||
path,
|
||
ERR_INCOMPATIBLE_API,
|
||
"Server is using an incompatible API version." );
|
||
|
||
return 0;
|
||
}
|
||
|
||
c->pid = pid;
|
||
c->capabilities = strdup( capabilities );
|
||
c->addr = lo_address_new_from_url( lo_address_get_url( lo_message_get_source( msg ) ));
|
||
c->name = strdup( client_name );
|
||
c->active = true;
|
||
|
||
MESSAGE( "Process has pid: %i", pid );
|
||
|
||
if ( ! expected_client )
|
||
client.push_back( c );
|
||
|
||
MESSAGE( "The client \"%s\" at \"%s\" informs us it's ready to receive commands.", &argv[0]->s, lo_address_get_url( c->addr ) );
|
||
|
||
osc_server->send( lo_message_get_source( msg ), "/reply",
|
||
path,
|
||
expected_client ?
|
||
"Howdy, what took you so long?" :
|
||
"Well hello, stranger. Welcome to the party.",
|
||
APP_TITLE,
|
||
":server-control:broadcast:optional-gui:" );
|
||
|
||
if ( gui_is_active )
|
||
{
|
||
osc_server->send( gui_addr, "/nsm/gui/client/new", c->client_id, c->name );
|
||
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "open" );
|
||
|
||
if ( c->is_capable_of( ":optional-gui:" ) )
|
||
osc_server->send( gui_addr, "/nsm/gui/client/has_optional_gui", c->client_id );
|
||
}
|
||
|
||
{
|
||
char *full_client_id;
|
||
asprintf( &full_client_id, "%s.%s", c->name, c->client_id );
|
||
|
||
char *client_project_path = get_client_project_path( session_path, c );
|
||
|
||
osc_server->send( lo_message_get_source( msg ), "/nsm/client/open", client_project_path, session_name, full_client_id );
|
||
|
||
c->pending_command( COMMAND_OPEN );
|
||
|
||
free( full_client_id );
|
||
free( client_project_path );
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
void
|
||
save_session_file ( )
|
||
{
|
||
char *session_file = NULL;
|
||
asprintf( &session_file, "%s/session.nsm", session_path );
|
||
|
||
FILE *fp = fopen( session_file, "w+" );
|
||
|
||
/* FIXME: handle errors. */
|
||
|
||
for ( std::list<Client*>::iterator i = client.begin();
|
||
i != client.end();
|
||
++i )
|
||
{
|
||
fprintf( fp, "%s:%s:%s\n", (*i)->name, (*i)->executable_path, (*i)->client_id );
|
||
}
|
||
|
||
fclose( fp );
|
||
}
|
||
|
||
Client *
|
||
client_by_name ( const char *name,
|
||
std::list<Client*> *cl )
|
||
{
|
||
for ( std::list<Client*>::iterator i = cl->begin();
|
||
i != cl->end();
|
||
++i )
|
||
{
|
||
if ( !strcmp( name, (*i)->name ) )
|
||
return *i;
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
bool
|
||
dumb_clients_are_alive ( )
|
||
{
|
||
std::list<Client*> *cl = &client;
|
||
|
||
for ( std::list<Client*>::iterator i = cl->begin();
|
||
i != cl->end();
|
||
++i )
|
||
{
|
||
if ( (*i)->is_dumb_client() && (*i)->pid > 0 )
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
void
|
||
wait_for_dumb_clients_to_die ( )
|
||
{
|
||
struct signalfd_siginfo fdsi;
|
||
|
||
GUIMSG( "Waiting for any dumb clients to die." );
|
||
|
||
for ( int i = 0; i < 6; i++ )
|
||
{
|
||
MESSAGE( "Loop %i", i );
|
||
|
||
if ( ! dumb_clients_are_alive() )
|
||
break;
|
||
|
||
ssize_t s = read(signal_fd, &fdsi, sizeof(struct signalfd_siginfo));
|
||
|
||
if (s == sizeof(struct signalfd_siginfo))
|
||
{
|
||
if (fdsi.ssi_signo == SIGCHLD)
|
||
handle_sigchld();
|
||
}
|
||
|
||
usleep( 50000 );
|
||
}
|
||
|
||
GUIMSG( "Done waiting" );
|
||
|
||
/* FIXME: give up on remaining clients and purge them */
|
||
}
|
||
|
||
|
||
bool
|
||
killed_clients_are_alive ( )
|
||
{
|
||
std::list<Client*> *cl = &client;
|
||
|
||
for ( std::list<Client*>::iterator i = cl->begin();
|
||
i != cl->end();
|
||
++i )
|
||
{
|
||
if ( ( (*i)->pending_command() == COMMAND_QUIT ||
|
||
(*i)->pending_command() == COMMAND_KILL ) &&
|
||
(*i)->pid > 0 )
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
void
|
||
wait_for_killed_clients_to_die ( )
|
||
{
|
||
struct signalfd_siginfo fdsi;
|
||
|
||
MESSAGE( "Waiting for killed clients to die." );
|
||
|
||
for ( int i = 0; i < 60; i++ )
|
||
{
|
||
MESSAGE( "Loop %i", i );
|
||
|
||
if ( ! killed_clients_are_alive() )
|
||
goto done;
|
||
|
||
ssize_t s = read(signal_fd, &fdsi, sizeof(struct signalfd_siginfo));
|
||
|
||
if (s == sizeof(struct signalfd_siginfo))
|
||
{
|
||
if (fdsi.ssi_signo == SIGCHLD)
|
||
handle_sigchld();
|
||
}
|
||
|
||
purge_dead_clients();
|
||
|
||
/* check OSC so we can get /progress messages. */
|
||
osc_server->check();
|
||
|
||
sleep(1);
|
||
}
|
||
|
||
WARNING( "Killed clients are still alive" );
|
||
|
||
return;
|
||
|
||
done:
|
||
|
||
MESSAGE( "All clients have died." );
|
||
}
|
||
|
||
|
||
void
|
||
command_all_clients_to_save ( )
|
||
{
|
||
if ( session_path )
|
||
{
|
||
GUIMSG( "Commanding attached clients to save." );
|
||
|
||
for ( std::list<Client*>::iterator i = client.begin();
|
||
i != client.end();
|
||
++i )
|
||
{
|
||
command_client_to_save( *i );
|
||
}
|
||
|
||
wait_for_replies();
|
||
|
||
save_session_file();
|
||
}
|
||
}
|
||
|
||
void
|
||
command_client_to_stop ( Client *c )
|
||
{
|
||
GUIMSG( "Stopping client %s", c->name );
|
||
|
||
if ( c->pid > 0 )
|
||
{
|
||
c->pending_command( COMMAND_KILL );
|
||
|
||
kill( c->pid, SIGTERM );
|
||
|
||
if ( gui_is_active )
|
||
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "stopped" );
|
||
}
|
||
}
|
||
|
||
void
|
||
command_client_to_quit ( Client *c )
|
||
{
|
||
MESSAGE( "Commanding %s to quit", c->name );
|
||
|
||
if ( c->active )
|
||
{
|
||
c->pending_command( COMMAND_QUIT );
|
||
|
||
kill( c->pid, SIGTERM );
|
||
|
||
if ( gui_is_active )
|
||
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "quit" );
|
||
}
|
||
else if ( c->is_dumb_client() )
|
||
{
|
||
if ( c->pid > 0 )
|
||
{
|
||
if ( gui_is_active )
|
||
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "quit" );
|
||
|
||
/* should be kill? */
|
||
c->pending_command( COMMAND_QUIT );
|
||
|
||
// this is a dumb client... try and kill it
|
||
kill( c->pid, SIGTERM );
|
||
}
|
||
else
|
||
{
|
||
if ( gui_is_active )
|
||
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "removed" );
|
||
}
|
||
}
|
||
}
|
||
|
||
void
|
||
close_session ( )
|
||
{
|
||
if ( ! session_path )
|
||
return;
|
||
|
||
for ( std::list<Client*>::iterator i = client.begin();
|
||
i != client.end();
|
||
++i )
|
||
{
|
||
command_client_to_quit( *i );
|
||
}
|
||
|
||
wait_for_killed_clients_to_die();
|
||
|
||
purge_inactive_clients();
|
||
|
||
clear_clients();
|
||
|
||
if ( session_path )
|
||
{
|
||
char *session_lock;
|
||
asprintf( &session_lock, "%s/.lock", session_path );
|
||
|
||
release_lock( &session_lock_fd, session_lock );
|
||
|
||
free(session_lock);
|
||
|
||
free(session_path);
|
||
session_path = NULL;
|
||
free(session_name);
|
||
session_name = NULL;
|
||
}
|
||
|
||
if ( gui_is_active )
|
||
{
|
||
osc_server->send( gui_addr, "/nsm/gui/session/name", "", "" );
|
||
}
|
||
}
|
||
|
||
void
|
||
tell_client_session_is_loaded( Client *c )
|
||
{
|
||
if ( c->active )
|
||
//!c->is_dumb_client() )
|
||
{
|
||
MESSAGE( "Telling client %s that session is loaded.", c->name );
|
||
osc_server->send( c->addr, "/nsm/client/session_is_loaded" );
|
||
}
|
||
}
|
||
|
||
void
|
||
tell_all_clients_session_is_loaded ( void )
|
||
{
|
||
MESSAGE( "Telling all clients that session is loaded..." );
|
||
|
||
for ( std::list<Client*>::iterator i = client.begin();
|
||
i != client.end();
|
||
++i )
|
||
{
|
||
tell_client_session_is_loaded( *i );
|
||
}
|
||
}
|
||
|
||
int
|
||
load_session_file ( const char * path )
|
||
{
|
||
char *session_file = NULL;
|
||
asprintf( &session_file, "%s/session.nsm", path );
|
||
char *session_lock = NULL;
|
||
asprintf( &session_lock, "%s/.lock", path );
|
||
|
||
if ( ! acquire_lock( &session_lock_fd, session_lock ) )
|
||
{
|
||
free( session_file );
|
||
free( session_lock );
|
||
|
||
WARNING( "Session is locked by another process" );
|
||
return ERR_SESSION_LOCKED;
|
||
}
|
||
|
||
|
||
FILE *fp;
|
||
|
||
if ( ! ( fp = fopen( session_file, "r" ) ) )
|
||
{
|
||
free( session_file );
|
||
return ERR_CREATE_FAILED;
|
||
}
|
||
|
||
session_path = strdup( path );
|
||
|
||
set_name( path );
|
||
|
||
std::list<Client*> new_clients;
|
||
|
||
{
|
||
char * client_name = NULL;
|
||
char * client_executable = NULL;
|
||
char * client_id = NULL;
|
||
|
||
// load the client list
|
||
while ( fscanf( fp, "%a[^:]:%a[^:]:%a[^:\n]\n", &client_name, &client_executable, &client_id ) > 0 )
|
||
{
|
||
Client *c = new Client();
|
||
|
||
c->name = client_name;
|
||
c->executable_path = client_executable;
|
||
c->client_id = client_id;
|
||
|
||
new_clients.push_back( c );
|
||
}
|
||
}
|
||
|
||
MESSAGE( "Commanding unneeded and dumb clients to quit" );
|
||
|
||
std::map<std::string,int> client_map;
|
||
|
||
/* count how many instances of each client are needed in the new session */
|
||
for ( std::list<Client*>::iterator i = new_clients.begin();
|
||
i != new_clients.end();
|
||
++i )
|
||
{
|
||
if ( client_map.find( (*i)->name) != client_map.end() )
|
||
client_map[(*i)->name]++;
|
||
else
|
||
client_map[(*i)->name] = 1;
|
||
}
|
||
|
||
for ( std::list<Client*>::iterator i = client.begin();
|
||
i != client.end();
|
||
++i )
|
||
{
|
||
if ( ! (*i)->is_capable_of( ":switch:" ) || client_map.find((*i)->name ) == client_map.end() )
|
||
{
|
||
/* client is not capable of switch, or is not wanted in the new session */
|
||
command_client_to_quit( *i );
|
||
}
|
||
else
|
||
{
|
||
/* client is switch capable and may be wanted in the new session */
|
||
if ( client_map[ (*i)->name ]-- <= 0 )
|
||
/* nope,, we already have as many as we need, stop this one */
|
||
command_client_to_quit( *i );
|
||
}
|
||
|
||
}
|
||
|
||
// wait_for_replies();
|
||
|
||
wait_for_killed_clients_to_die();
|
||
|
||
// wait_for_dumb_clients_to_die();
|
||
|
||
purge_inactive_clients();
|
||
|
||
for ( std::list<Client*>::iterator i = client.begin();
|
||
i != client.end();
|
||
++i )
|
||
{
|
||
(*i)->pre_existing = true;
|
||
}
|
||
|
||
MESSAGE( "Commanding smart clients to switch" );
|
||
|
||
for ( std::list<Client*>::iterator i = new_clients.begin();
|
||
i != new_clients.end();
|
||
++i )
|
||
{
|
||
Client *c = NULL;
|
||
|
||
/* in a duplicated session, clients will have the same
|
||
* IDs, so be sure to pick the right one to avoid race
|
||
* conditions in JACK name registration. */
|
||
c = get_client_by_name_and_id( &client, (*i)->name, (*i)->client_id );
|
||
|
||
if ( ! c )
|
||
c = client_by_name( (*i)->name, &client );
|
||
|
||
if ( c && c->pre_existing && !c->reply_pending() )
|
||
{
|
||
// since we already shutdown clients not capable of 'switch', we can assume that these are.
|
||
command_client_to_switch( c, (*i)->client_id );
|
||
}
|
||
else
|
||
{
|
||
/* sleep a little bit because liblo derives its sequence
|
||
* of port numbers from the system time (second
|
||
* resolution) and if too many clients start at once they
|
||
* won't be able to find a free port. */
|
||
usleep( 100 * 1000 );
|
||
|
||
launch( (*i)->executable_path, (*i)->client_id );
|
||
}
|
||
}
|
||
|
||
/* this part is a little tricky... the clients need some time to
|
||
* send their 'announce' messages before we can send them 'open'
|
||
* and know that a reply is pending and we should continue waiting
|
||
* until they finish. wait_for_replies() must check for OSC
|
||
* messages immediately, even if no replies seem to be pending
|
||
* yet. */
|
||
|
||
/* dumb clients will never send an 'announce message', so we need
|
||
* to give up waiting on them fairly soon. */
|
||
|
||
wait_for_announce();
|
||
|
||
wait_for_replies();
|
||
|
||
tell_all_clients_session_is_loaded();
|
||
|
||
MESSAGE( "Loaded." );
|
||
|
||
new_clients.clear();
|
||
|
||
if ( gui_is_active )
|
||
{
|
||
osc_server->send( gui_addr, "/nsm/gui/session/name", session_name, session_path + strlen( session_root ));
|
||
}
|
||
|
||
return ERR_OK;
|
||
}
|
||
|
||
OSC_HANDLER( save )
|
||
{
|
||
if ( pending_operation != COMMAND_NONE )
|
||
{
|
||
osc_server->send( lo_message_get_source( msg ), "/error", path,
|
||
ERR_OPERATION_PENDING,
|
||
"An operation pending." );
|
||
return 0;
|
||
}
|
||
|
||
if ( ! session_path )
|
||
{
|
||
osc_server->send( lo_message_get_source( msg ), "/error", path,
|
||
ERR_NO_SESSION_OPEN,
|
||
"No session to save.");
|
||
|
||
goto done;
|
||
}
|
||
|
||
command_all_clients_to_save();
|
||
|
||
MESSAGE( "Done." );
|
||
|
||
osc_server->send( lo_message_get_source( msg ), "/reply", path, "Saved." );
|
||
|
||
done:
|
||
|
||
pending_operation = COMMAND_NONE;
|
||
|
||
return 0;
|
||
}
|
||
|
||
OSC_HANDLER( duplicate )
|
||
{
|
||
if ( pending_operation != COMMAND_NONE )
|
||
{
|
||
osc_server->send( lo_message_get_source( msg ), "/error", path,
|
||
ERR_OPERATION_PENDING,
|
||
"An operation pending." );
|
||
return 0;
|
||
}
|
||
|
||
pending_operation = COMMAND_DUPLICATE;
|
||
|
||
if ( ! session_path )
|
||
{
|
||
osc_server->send( lo_message_get_source( msg ), "/error", path,
|
||
ERR_NO_SESSION_OPEN,
|
||
"No session to duplicate.");
|
||
goto done;
|
||
}
|
||
|
||
if ( ! path_is_valid( &argv[0]->s ) )
|
||
{
|
||
osc_server->send( lo_message_get_source( msg ), "/error", path,
|
||
ERR_CREATE_FAILED,
|
||
"Invalid session name." );
|
||
|
||
goto done;
|
||
}
|
||
|
||
command_all_clients_to_save();
|
||
|
||
if ( clients_have_errors() )
|
||
{
|
||
osc_server->send( lo_message_get_source( msg ), "/error", path,
|
||
ERR_GENERAL_ERROR,
|
||
"Some clients could not save" );
|
||
|
||
goto done;
|
||
}
|
||
|
||
// save_session_file();
|
||
|
||
char *spath;
|
||
asprintf( &spath, "%s/%s", session_root, &argv[0]->s );
|
||
|
||
mkpath( spath, false );
|
||
|
||
/* FIXME: code a recursive copy instead of calling the shell */
|
||
|
||
char *cmd;
|
||
asprintf( &cmd, "cp -R \"%s\" \"%s\"", session_path, spath);
|
||
|
||
system( cmd );
|
||
|
||
free( cmd );
|
||
|
||
osc_server->send( gui_addr, "/nsm/gui/session/session", &argv[0]->s );
|
||
|
||
MESSAGE( "Attempting to open %s", spath );
|
||
|
||
if ( !load_session_file( spath ) )
|
||
{
|
||
MESSAGE( "Loaded" );
|
||
osc_server->send( lo_message_get_source( msg ), "/reply", path,
|
||
"Loaded." );
|
||
}
|
||
else
|
||
{
|
||
MESSAGE( "Failed" );
|
||
osc_server->send( lo_message_get_source( msg ), "/error", path,
|
||
ERR_NO_SUCH_FILE,
|
||
"No such file." );
|
||
free(spath);
|
||
return -1;
|
||
}
|
||
|
||
free( spath );
|
||
|
||
MESSAGE( "Done" );
|
||
|
||
osc_server->send( lo_message_get_source( msg ), "/reply", path, "Duplicated." );
|
||
|
||
done:
|
||
|
||
pending_operation = COMMAND_NONE;
|
||
|
||
return 0;
|
||
}
|
||
|
||
OSC_HANDLER( new )
|
||
{
|
||
if ( pending_operation != COMMAND_NONE )
|
||
{
|
||
osc_server->send( lo_message_get_source( msg ), "/error", path,
|
||
ERR_OPERATION_PENDING,
|
||
"An operation pending." );
|
||
return 0;
|
||
}
|
||
|
||
pending_operation = COMMAND_NEW;
|
||
|
||
if ( ! path_is_valid( &argv[0]->s ) )
|
||
{
|
||
osc_server->send( lo_message_get_source( msg ), "/error", path,
|
||
ERR_CREATE_FAILED,
|
||
"Invalid session name." );
|
||
|
||
pending_operation = COMMAND_NONE;
|
||
|
||
return 0;
|
||
}
|
||
|
||
if ( session_path )
|
||
{
|
||
command_all_clients_to_save();
|
||
|
||
close_session();
|
||
}
|
||
|
||
GUIMSG( "Creating new session \"%s\"", &argv[0]->s );
|
||
|
||
char *spath;
|
||
asprintf( &spath, "%s/%s", session_root, &argv[0]->s );
|
||
|
||
if ( mkpath( spath, true ) )
|
||
{
|
||
osc_server->send( lo_message_get_source( msg ), "/error", path,
|
||
ERR_CREATE_FAILED,
|
||
"Could not create the session directory" );
|
||
|
||
free(spath);
|
||
|
||
pending_operation = COMMAND_NONE;
|
||
|
||
return 0;
|
||
}
|
||
|
||
session_path = strdup( spath );
|
||
|
||
set_name( session_path );
|
||
|
||
osc_server->send( lo_message_get_source( msg ), "/reply", path, "Created." );
|
||
|
||
if ( gui_is_active )
|
||
{
|
||
osc_server->send( gui_addr, "/nsm/gui/session/session", &argv[0]->s );
|
||
|
||
osc_server->send( gui_addr, "/nsm/gui/session/name", &argv[0]->s, &argv[0]->s );
|
||
}
|
||
|
||
save_session_file();
|
||
|
||
free( spath );
|
||
|
||
osc_server->send( lo_message_get_source( msg ), "/reply", path,
|
||
"Session created" );
|
||
|
||
pending_operation = COMMAND_NONE;
|
||
|
||
return 0;
|
||
}
|
||
|
||
static lo_address list_response_address;
|
||
|
||
int
|
||
list_file ( const char *fpath, const struct stat *sb, int tflag )
|
||
{
|
||
char *s;
|
||
|
||
if ( tflag == FTW_F )
|
||
{
|
||
s = strdup( fpath );
|
||
if ( ! strcmp( "session.nsm", basename( s ) ) )
|
||
{
|
||
free( s );
|
||
s = strdup( fpath );
|
||
|
||
s = dirname( s );
|
||
|
||
memmove( s, s + strlen( session_root ) + 1, (strlen( s ) - strlen( session_root )) + 1);
|
||
|
||
osc_server->send( list_response_address, "/reply", "/nsm/server/list", s );
|
||
|
||
free( s );
|
||
}
|
||
else
|
||
free( s );
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
OSC_HANDLER( list )
|
||
{
|
||
GUIMSG( "Listing sessions" );
|
||
|
||
list_response_address = lo_message_get_source( msg );
|
||
|
||
ftw( session_root, list_file, 20 );
|
||
|
||
osc_server->send( lo_message_get_source( msg ), path,
|
||
ERR_OK,
|
||
"Done." );
|
||
return 0;
|
||
}
|
||
|
||
OSC_HANDLER( open )
|
||
{
|
||
GUIMSG( "Opening session %s", &argv[0]->s );
|
||
|
||
if ( pending_operation != COMMAND_NONE )
|
||
{
|
||
osc_server->send( lo_message_get_source( msg ), "/error", path,
|
||
ERR_OPERATION_PENDING,
|
||
"An operation pending." );
|
||
return 0;
|
||
}
|
||
|
||
pending_operation = COMMAND_OPEN;
|
||
|
||
|
||
if ( session_path )
|
||
{
|
||
|
||
command_all_clients_to_save();
|
||
|
||
if ( clients_have_errors() )
|
||
{
|
||
osc_server->send( lo_message_get_source( msg ), "/error", path,
|
||
ERR_GENERAL_ERROR,
|
||
"Some clients could not save" );
|
||
|
||
pending_operation = COMMAND_NONE;
|
||
return 0;
|
||
}
|
||
|
||
// save_session_file();
|
||
}
|
||
|
||
|
||
char *spath;
|
||
asprintf( &spath, "%s/%s", session_root, &argv[0]->s );
|
||
|
||
MESSAGE( "Attempting to open %s", spath );
|
||
|
||
int err = load_session_file( spath );
|
||
|
||
if ( ! err )
|
||
{
|
||
MESSAGE( "Loaded" );
|
||
osc_server->send( lo_message_get_source( msg ), "/reply", path,
|
||
"Loaded." );
|
||
}
|
||
else
|
||
{
|
||
MESSAGE( "Failed" );
|
||
const char *m = NULL;
|
||
switch ( err )
|
||
{
|
||
case ERR_CREATE_FAILED:
|
||
m = "Could not create session file!";
|
||
break;
|
||
case ERR_SESSION_LOCKED:
|
||
m = "Session is locked by another process!";
|
||
break;
|
||
case ERR_NO_SUCH_FILE:
|
||
m = "The named session does not exist.";
|
||
break;
|
||
default:
|
||
m = "Unknown error";
|
||
}
|
||
|
||
|
||
osc_server->send( lo_message_get_source( msg ), "/error", path,
|
||
err,
|
||
m );
|
||
}
|
||
|
||
free( spath );
|
||
|
||
MESSAGE( "Done" );
|
||
|
||
pending_operation = COMMAND_NONE;
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
OSC_HANDLER( quit )
|
||
{
|
||
close_session();
|
||
|
||
exit(0);
|
||
|
||
return 0;
|
||
}
|
||
|
||
OSC_HANDLER( abort )
|
||
{
|
||
if ( pending_operation != COMMAND_NONE )
|
||
{
|
||
osc_server->send( lo_message_get_source( msg ), "/error", path,
|
||
ERR_OPERATION_PENDING,
|
||
"An operation pending." );
|
||
return 0;
|
||
}
|
||
|
||
pending_operation = COMMAND_CLOSE;
|
||
|
||
|
||
if ( ! session_path )
|
||
{
|
||
osc_server->send( lo_message_get_source( msg ), "/error", path,
|
||
ERR_NO_SESSION_OPEN,
|
||
"No session to abort." );
|
||
|
||
goto done;
|
||
}
|
||
|
||
GUIMSG( "Commanding attached clients to quit." );
|
||
|
||
close_session();
|
||
|
||
osc_server->send( lo_message_get_source( msg ), "/reply", path,
|
||
"Aborted." );
|
||
|
||
MESSAGE( "Done" );
|
||
|
||
done:
|
||
|
||
pending_operation = COMMAND_NONE;
|
||
|
||
return 0;
|
||
}
|
||
|
||
OSC_HANDLER( close )
|
||
{
|
||
if ( pending_operation != COMMAND_NONE )
|
||
{
|
||
osc_server->send( lo_message_get_source( msg ), "/error", path,
|
||
ERR_OPERATION_PENDING,
|
||
"An operation pending." );
|
||
return 0;
|
||
}
|
||
|
||
pending_operation = COMMAND_CLOSE;
|
||
|
||
if ( ! session_path )
|
||
{
|
||
osc_server->send( lo_message_get_source( msg ), "/error", path,
|
||
ERR_NO_SESSION_OPEN,
|
||
"No session to close." );
|
||
|
||
goto done;
|
||
}
|
||
command_all_clients_to_save();
|
||
|
||
GUIMSG( "Commanding attached clients to quit." );
|
||
|
||
close_session();
|
||
|
||
osc_server->send( lo_message_get_source( msg ), "/reply", path,
|
||
"Closed." );
|
||
|
||
MESSAGE( "Done" );
|
||
|
||
done:
|
||
|
||
pending_operation = COMMAND_NONE;
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
OSC_HANDLER( broadcast )
|
||
{
|
||
const char *to_path = &argv[0]->s;
|
||
|
||
/* don't allow clients to broadcast NSM commands */
|
||
if ( ! strncmp( to_path, "/nsm/", strlen( "/nsm/" ) ) )
|
||
return 0;
|
||
|
||
std::list<OSC::OSC_Value> new_args;
|
||
|
||
for ( int i = 1; i < argc; ++i )
|
||
{
|
||
switch ( types[i] )
|
||
{
|
||
case 's':
|
||
new_args.push_back( OSC::OSC_String( &argv[i]->s ) );
|
||
break;
|
||
case 'i':
|
||
new_args.push_back( OSC::OSC_Int( argv[i]->i ) );
|
||
break;
|
||
case 'f':
|
||
new_args.push_back( OSC::OSC_Float( argv[i]->f ) );
|
||
break;
|
||
}
|
||
}
|
||
|
||
char *sender_url = lo_address_get_url( lo_message_get_source( msg ) );
|
||
|
||
for ( std::list<Client*>::iterator i = client.begin();
|
||
i != client.end();
|
||
++i )
|
||
{
|
||
if ( ! (*i)->addr )
|
||
continue;
|
||
|
||
char *url = lo_address_get_url( (*i)->addr );
|
||
|
||
if ( strcmp( sender_url, url ) )
|
||
{
|
||
osc_server->send( (*i)->addr, to_path, new_args );
|
||
}
|
||
|
||
free( url );
|
||
}
|
||
|
||
/* also relay to attached GUI so that the broadcast can be
|
||
* propagated to another NSMD instance */
|
||
if ( gui_is_active )
|
||
{
|
||
|
||
char *u1 = lo_address_get_url( gui_addr );
|
||
|
||
if ( strcmp( u1, sender_url ) )
|
||
{
|
||
new_args.push_front( OSC::OSC_String( to_path ) );
|
||
|
||
osc_server->send( gui_addr, path, new_args );
|
||
}
|
||
|
||
free(u1);
|
||
}
|
||
|
||
free( sender_url );
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
|
||
/*********************************/
|
||
/* Client Informational Messages */
|
||
/*********************************/
|
||
|
||
OSC_HANDLER( progress )
|
||
{
|
||
Client *c = get_client_by_address( lo_message_get_source( msg ) );
|
||
|
||
if ( c )
|
||
{
|
||
c->progress = argv[0]->f;
|
||
|
||
/* MESSAGE( "%s progress: %i%%", c->name, (int)(c->progress * 100.0f) ); */
|
||
|
||
if ( gui_is_active )
|
||
{
|
||
osc_server->send( gui_addr, "/nsm/gui/client/progress", c->client_id, (float)c->progress );
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
OSC_HANDLER( is_dirty )
|
||
{
|
||
MESSAGE( "Client sends dirty" );
|
||
|
||
Client *c = get_client_by_address( lo_message_get_source( msg ) );
|
||
|
||
if ( ! c )
|
||
return 0;
|
||
|
||
c->dirty = 1;
|
||
|
||
if ( gui_is_active )
|
||
osc_server->send( gui_addr, "/nsm/gui/client/dirty", c->client_id, c->dirty );
|
||
|
||
return 0;
|
||
}
|
||
|
||
OSC_HANDLER( is_clean )
|
||
{
|
||
MESSAGE( "Client sends clean" );
|
||
|
||
Client *c = get_client_by_address( lo_message_get_source( msg ) );
|
||
|
||
if ( ! c )
|
||
return 0;
|
||
|
||
c->dirty = 0;
|
||
|
||
if ( gui_is_active )
|
||
osc_server->send( gui_addr, "/nsm/gui/client/dirty", c->client_id, c->dirty );
|
||
|
||
return 0;
|
||
}
|
||
|
||
OSC_HANDLER( gui_is_hidden )
|
||
{
|
||
MESSAGE( "Client sends gui hidden" );
|
||
|
||
Client *c = get_client_by_address( lo_message_get_source( msg ) );
|
||
|
||
if ( ! c )
|
||
return 0;
|
||
|
||
c->gui_visible( false );
|
||
|
||
if ( gui_is_active )
|
||
osc_server->send( gui_addr, "/nsm/gui/client/gui_visible", c->client_id, c->gui_visible() );
|
||
|
||
return 0;
|
||
}
|
||
|
||
OSC_HANDLER( gui_is_shown )
|
||
{
|
||
MESSAGE( "Client sends gui shown" );
|
||
|
||
Client *c = get_client_by_address( lo_message_get_source( msg ) );
|
||
|
||
if ( ! c )
|
||
return 0;
|
||
|
||
c->gui_visible( true );
|
||
|
||
if ( gui_is_active )
|
||
osc_server->send( gui_addr, "/nsm/gui/client/gui_visible", c->client_id, c->gui_visible() );
|
||
|
||
return 0;
|
||
}
|
||
|
||
OSC_HANDLER( message )
|
||
{
|
||
Client *c = get_client_by_address( lo_message_get_source( msg ) );
|
||
|
||
if ( ! c )
|
||
return 0;
|
||
|
||
if ( gui_is_active )
|
||
osc_server->send( gui_addr, "/nsm/gui/client/message", c->client_id, argv[0]->i, &argv[1]->s );
|
||
|
||
return 0;
|
||
}
|
||
|
||
OSC_HANDLER( label )
|
||
{
|
||
Client *c = get_client_by_address( lo_message_get_source( msg ) );
|
||
|
||
if ( ! c )
|
||
return 0;
|
||
|
||
if ( strcmp( types, "s" ) )
|
||
return -1;
|
||
|
||
c->label( &argv[0]->s );
|
||
|
||
if ( gui_is_active )
|
||
osc_server->send( gui_addr, "/nsm/gui/client/label", c->client_id, &argv[0]->s );
|
||
|
||
return 0;
|
||
}
|
||
|
||
/**********************/
|
||
/* Response Handlers */
|
||
/**********************/
|
||
|
||
|
||
OSC_HANDLER( error )
|
||
{
|
||
Client *c = get_client_by_address( lo_message_get_source( msg ) );
|
||
|
||
if ( ! c )
|
||
{
|
||
WARNING( "Error from unknown client" );
|
||
return 0;
|
||
}
|
||
|
||
// const char *rpath = &argv[0]->s;
|
||
|
||
int err_code = argv[1]->i;
|
||
|
||
const char *message = &argv[2]->s;
|
||
|
||
c->set_reply( err_code, message );
|
||
|
||
MESSAGE( "Client \"%s\" replied with error: %s (%i) in %fms", c->name, message, err_code, c->milliseconds_since_last_command() );
|
||
c->pending_command( COMMAND_NONE );
|
||
|
||
if ( gui_is_active )
|
||
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "error" );
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
OSC_HANDLER( reply )
|
||
{
|
||
Client *c = get_client_by_address( lo_message_get_source( msg ) );
|
||
|
||
// const char *rpath = &argv[0]->s;
|
||
const char *message = &argv[1]->s;
|
||
|
||
if ( c )
|
||
{
|
||
c->set_reply( ERR_OK, message );
|
||
|
||
MESSAGE( "Client \"%s\" replied with: %s in %fms", c->name, message, c->milliseconds_since_last_command() );
|
||
|
||
c->pending_command( COMMAND_NONE );
|
||
|
||
if ( gui_is_active )
|
||
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "ready" );
|
||
}
|
||
else
|
||
MESSAGE( "Reply from unknown client" );
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
|
||
/******************/
|
||
/* GUI operations */
|
||
/******************/
|
||
|
||
|
||
OSC_HANDLER( stop )
|
||
{
|
||
Client *c = get_client_by_id( &client, &argv[0]->s );
|
||
|
||
if ( c )
|
||
{
|
||
command_client_to_stop( c );
|
||
|
||
if ( gui_is_active )
|
||
osc_server->send( gui_addr, "/reply", "Client stopped." );
|
||
}
|
||
else
|
||
{
|
||
if ( gui_is_active )
|
||
osc_server->send( gui_addr, "/error", -10, "No such client." );
|
||
}
|
||
|
||
|
||
return 0;
|
||
}
|
||
|
||
OSC_HANDLER( remove )
|
||
{
|
||
Client *c = get_client_by_id( &client, &argv[0]->s );
|
||
|
||
if ( c )
|
||
{
|
||
if ( c->pid == 0 &&
|
||
! c->active )
|
||
{
|
||
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "removed" );
|
||
|
||
client.remove( c );
|
||
|
||
delete c;
|
||
|
||
if ( gui_is_active )
|
||
osc_server->send( gui_addr, "/reply", "Client removed." );
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if ( gui_is_active )
|
||
osc_server->send( gui_addr, "/error", -10, "No such client." );
|
||
}
|
||
|
||
|
||
return 0;
|
||
}
|
||
|
||
OSC_HANDLER( resume )
|
||
{
|
||
Client *c = get_client_by_id( &client, &argv[0]->s );
|
||
|
||
/* FIXME: return error if no such client? */
|
||
if ( c )
|
||
{
|
||
if ( c->pid == 0 &&
|
||
! c->active )
|
||
{
|
||
if ( ! launch( c->executable_path, c->client_id ) )
|
||
{
|
||
|
||
}
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
OSC_HANDLER( client_save )
|
||
{
|
||
Client *c = get_client_by_id( &client, &argv[0]->s );
|
||
|
||
/* FIXME: return error if no such client? */
|
||
if ( c )
|
||
{
|
||
if ( c->active )
|
||
{
|
||
command_client_to_save( c );
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
OSC_HANDLER( client_show_optional_gui )
|
||
{
|
||
Client *c = get_client_by_id( &client, &argv[0]->s );
|
||
|
||
/* FIXME: return error if no such client? */
|
||
if ( c )
|
||
{
|
||
if ( c->active )
|
||
{
|
||
osc_server->send( c->addr, "/nsm/client/show_optional_gui" );
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
OSC_HANDLER( client_hide_optional_gui )
|
||
{
|
||
Client *c = get_client_by_id( &client, &argv[0]->s );
|
||
|
||
/* FIXME: return error if no such client? */
|
||
if ( c )
|
||
{
|
||
if ( c->active )
|
||
{
|
||
osc_server->send( c->addr, "/nsm/client/hide_optional_gui" );
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
void
|
||
announce_gui( const char *url, bool is_reply )
|
||
{
|
||
gui_addr = lo_address_new_from_url( url );
|
||
gui_is_active = true;
|
||
|
||
if ( is_reply )
|
||
osc_server->send( gui_addr, "/nsm/gui/gui_announce", "hi" );
|
||
else
|
||
osc_server->send( gui_addr, "/nsm/gui/server_announce", "hi" );
|
||
|
||
for ( std::list<Client*>::iterator i = client.begin();
|
||
i != client.end();
|
||
++i )
|
||
{
|
||
Client *c = *i;
|
||
|
||
osc_server->send( gui_addr, "/nsm/gui/client/new", c->client_id, c->name );
|
||
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status );
|
||
|
||
}
|
||
|
||
osc_server->send( gui_addr, "/nsm/gui/session/name", session_name ? session_name : "", session_path ? session_path : "" );
|
||
|
||
DMESSAGE( "Registered with GUI" );
|
||
}
|
||
|
||
|
||
OSC_HANDLER( gui_announce )
|
||
{
|
||
announce_gui( lo_address_get_url( lo_message_get_source( msg ) ), true );
|
||
|
||
return 0;
|
||
}
|
||
|
||
OSC_HANDLER( ping )
|
||
{
|
||
osc_server->send( lo_message_get_source( msg ), "/reply", path );
|
||
|
||
return 0;
|
||
}
|
||
|
||
OSC_HANDLER( null )
|
||
{
|
||
WARNING( "Unrecognized message with type signature \"%s\" at path \"%s\"", types, path );
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
|
||
static void
|
||
wait ( long timeout )
|
||
{
|
||
struct signalfd_siginfo fdsi;
|
||
|
||
ssize_t s = read(signal_fd, &fdsi, sizeof(struct signalfd_siginfo));
|
||
|
||
if (s == sizeof(struct signalfd_siginfo))
|
||
{
|
||
if (fdsi.ssi_signo == SIGCHLD)
|
||
handle_sigchld();
|
||
}
|
||
|
||
osc_server->wait( timeout );
|
||
|
||
purge_dead_clients();
|
||
}
|
||
|
||
int main(int argc, char *argv[])
|
||
{
|
||
sigset_t mask;
|
||
sigemptyset( &mask );
|
||
sigaddset( &mask, SIGCHLD );
|
||
sigprocmask(SIG_BLOCK, &mask, NULL );
|
||
signal_fd = signalfd( -1, &mask, SFD_NONBLOCK );
|
||
|
||
/* generate random seed for client ids */
|
||
{
|
||
time_t seconds;
|
||
time(&seconds);
|
||
|
||
srand( (unsigned int) seconds );
|
||
}
|
||
|
||
// char *osc_port = "6666";
|
||
char *osc_port = NULL;
|
||
const char *gui_url = NULL;
|
||
|
||
static struct option long_options[] =
|
||
{
|
||
{ "detach", no_argument, 0, 'd' },
|
||
{ "session-root", required_argument, 0, 's' },
|
||
{ "osc-port", required_argument, 0, 'p' },
|
||
{ "gui-url", required_argument, 0, 'g' },
|
||
{ "help", no_argument, 0, 'h' },
|
||
{ 0, 0, 0, 0 }
|
||
};
|
||
|
||
int option_index = 0;
|
||
int c = 0;
|
||
|
||
bool detach = false;
|
||
|
||
while ( ( c = getopt_long_only( argc, argv, "", long_options, &option_index ) ) != -1 )
|
||
{
|
||
switch ( c )
|
||
{
|
||
case 'd':
|
||
detach = true;
|
||
break;
|
||
case 's':
|
||
{
|
||
session_root = optarg;
|
||
|
||
/* get rid of trailing slash */
|
||
char *s = rindex(session_root,'/');
|
||
if ( s == &session_root[strlen(session_root) - 1] )
|
||
*s = '\0';
|
||
break;
|
||
}
|
||
case 'p':
|
||
DMESSAGE( "Using OSC port %s", optarg );
|
||
osc_port = optarg;
|
||
break;
|
||
case 'g':
|
||
DMESSAGE( "Going to connect to GUI at: %s", optarg );
|
||
gui_url = optarg;
|
||
break;
|
||
case 'h':
|
||
printf( "Usage: %s [--osc-port portnum] [--session-root path]\n\n", argv[0] );
|
||
exit(0);
|
||
break;
|
||
}
|
||
}
|
||
|
||
if ( !session_root )
|
||
asprintf( &session_root, "%s/%s", getenv( "HOME" ), "NSM Sessions" );
|
||
|
||
struct stat st;
|
||
|
||
if ( stat( session_root, &st ) )
|
||
{
|
||
if ( mkdir( session_root, 0771 ) )
|
||
{
|
||
FATAL( "Failed to create session directory: %s", strerror( errno ) );
|
||
}
|
||
}
|
||
|
||
MESSAGE( "Session root is: %s", session_root );
|
||
|
||
osc_server = new OSC::Endpoint();
|
||
|
||
if ( osc_server->init( LO_UDP, osc_port ) )
|
||
{
|
||
FATAL( "Failed to create OSC server." );
|
||
}
|
||
|
||
printf( "NSM_URL=%s\n", osc_server->url() );
|
||
|
||
if ( gui_url )
|
||
{
|
||
announce_gui( gui_url, false );
|
||
}
|
||
|
||
/* */
|
||
osc_server->add_method( "/nsm/server/announce", "sssiii", OSC_NAME( announce ), NULL, "client_name,capabilities,executable,api_version_major,api_version_minor,client_pid" );
|
||
|
||
/* response handlers */
|
||
osc_server->add_method( "/reply", "ss", OSC_NAME( reply ), NULL, "err_code,msg" );
|
||
osc_server->add_method( "/error", "sis", OSC_NAME( error ), NULL, "err_code,msg" );
|
||
|
||
osc_server->add_method( "/nsm/client/progress", "f", OSC_NAME( progress ), NULL, "progress" );
|
||
osc_server->add_method( "/nsm/client/is_dirty", "", OSC_NAME( is_dirty ), NULL, "dirtiness" );
|
||
osc_server->add_method( "/nsm/client/is_clean", "", OSC_NAME( is_clean ), NULL, "dirtiness" );
|
||
osc_server->add_method( "/nsm/client/message", "is", OSC_NAME( message ), NULL, "message" );
|
||
osc_server->add_method( "/nsm/client/gui_is_hidden", "", OSC_NAME( gui_is_hidden ), NULL, "message" );
|
||
osc_server->add_method( "/nsm/client/gui_is_shown", "", OSC_NAME( gui_is_shown ), NULL, "message" );
|
||
osc_server->add_method( "/nsm/client/label", "s", OSC_NAME( label ), NULL, "message" );
|
||
|
||
/* */
|
||
osc_server->add_method( "/nsm/gui/gui_announce", "", OSC_NAME( gui_announce ), NULL, "" );
|
||
osc_server->add_method( "/nsm/gui/client/stop", "s", OSC_NAME( stop ), NULL, "client_id" );
|
||
osc_server->add_method( "/nsm/gui/client/remove", "s", OSC_NAME( remove ), NULL, "client_id" );
|
||
osc_server->add_method( "/nsm/gui/client/resume", "s", OSC_NAME( resume ), NULL, "client_id" );
|
||
osc_server->add_method( "/nsm/gui/client/save", "s", OSC_NAME( client_save ), NULL, "client_id" );
|
||
osc_server->add_method( "/nsm/gui/client/show_optional_gui", "s", OSC_NAME( client_show_optional_gui ), NULL, "client_id" );
|
||
osc_server->add_method( "/nsm/gui/client/hide_optional_gui", "s", OSC_NAME( client_hide_optional_gui ), NULL, "client_id" );
|
||
|
||
osc_server->add_method( "/osc/ping", "", OSC_NAME( ping ), NULL, "" );
|
||
|
||
osc_server->add_method( "/nsm/server/broadcast", NULL, OSC_NAME( broadcast ), NULL, "" );
|
||
osc_server->add_method( "/nsm/server/duplicate", "s", OSC_NAME( duplicate ), NULL, "" );
|
||
osc_server->add_method( "/nsm/server/abort", "", OSC_NAME( abort ), NULL, "" );
|
||
osc_server->add_method( "/nsm/server/list", "", OSC_NAME( list ), NULL, "" );
|
||
osc_server->add_method( "/nsm/server/add", "s", OSC_NAME( add ), NULL, "executable_name" );
|
||
osc_server->add_method( "/nsm/server/new", "s", OSC_NAME( new ), NULL, "name" );
|
||
osc_server->add_method( "/nsm/server/save", "", OSC_NAME( save ), NULL, "" );
|
||
osc_server->add_method( "/nsm/server/open", "s", OSC_NAME( open ), NULL, "name" );
|
||
osc_server->add_method( "/nsm/server/close", "", OSC_NAME( close ), NULL, "" );
|
||
osc_server->add_method( "/nsm/server/quit", "", OSC_NAME( quit ), NULL, "" );
|
||
|
||
osc_server->add_method( NULL, NULL, OSC_NAME( null ),NULL, "" );
|
||
|
||
if ( detach )
|
||
{
|
||
MESSAGE( "Detaching from console" );
|
||
if ( fork() )
|
||
{
|
||
exit( 0 );
|
||
}
|
||
else
|
||
{
|
||
fclose( stdin );
|
||
fclose( stdout );
|
||
fclose( stderr );
|
||
}
|
||
}
|
||
|
||
/* listen for sigchld signals and process OSC messages forever */
|
||
for ( ;; )
|
||
{
|
||
wait( 1000 );
|
||
}
|
||
|
||
// osc_server->run();
|
||
|
||
return 0;
|
||
}
|