NSM/NSM Proxy: Add two new options. Stop Signal and Config File.
This commit is contained in:
parent
21ab68d3e5
commit
9b8f02fbf3
|
@ -7,31 +7,31 @@ class NSM_Proxy_UI {open
|
||||||
Function {make_window()} {open
|
Function {make_window()} {open
|
||||||
} {
|
} {
|
||||||
Fl_Window {} {
|
Fl_Window {} {
|
||||||
label {NSM Proxy} open
|
label {NSM Proxy} open selected
|
||||||
xywh {1011 106 490 665} type Double color 47 labelcolor 55 visible xclass NSM-Proxy
|
xywh {644 190 635 665} type Double color 47 labelcolor 55 xclass {NSM-Proxy} visible
|
||||||
} {
|
} {
|
||||||
Fl_Box {} {
|
Fl_Box {} {
|
||||||
label {Command-line options are incompatible with robust session management for a variety of reasons, so the NSM server does not support them directly. This proxy exists to allow programs which require command-line options to be included in an NSM session. Be warned that referring to files outside of the session directory will impair your ability to reliably archive and transport sessions. Patching the program to use NSM natively will result in a better experience.
|
label {Command-line options are incompatible with robust session management for a variety of reasons, so the NSM server does not support them directly. This proxy exists to allow programs which require command-line options to be included in an NSM session. Be warned that referring to files outside of the session directory will impair your ability to reliably archive and transport sessions. Patching the program to use NSM natively will result in a better experience.
|
||||||
|
|
||||||
The program will be started with its current directory being a uniquely named directory under the current session directory. It is recommended that you only refer to files in the current directory.
|
The program will be started with its current directory being a uniquely named directory under the current session directory. It is recommended that you only refer to files in the current directory.
|
||||||
} selected
|
}
|
||||||
xywh {15 11 460 233} box BORDER_BOX color 41 labelfont 8 labelcolor 55 align 128
|
xywh {15 11 610 139} box BORDER_BOX color 41 labelfont 8 labelsize 12 labelcolor 55 align 128
|
||||||
}
|
}
|
||||||
Fl_File_Input executable_input {
|
Fl_File_Input executable_input {
|
||||||
label {Executable: }
|
label {Executable: }
|
||||||
xywh {115 262 350 33}
|
xywh {115 162 495 31}
|
||||||
}
|
}
|
||||||
Fl_Input arguments_input {
|
Fl_Input arguments_input {
|
||||||
label {Arguments:}
|
label {Arguments:}
|
||||||
xywh {115 414 350 28}
|
xywh {110 310 350 28}
|
||||||
}
|
}
|
||||||
Fl_Input label_input {
|
Fl_Input label_input {
|
||||||
label {Label:}
|
label {Label:}
|
||||||
xywh {115 452 350 28}
|
xywh {110 340 350 28}
|
||||||
}
|
}
|
||||||
Fl_Return_Button start_button {
|
Fl_Return_Button start_button {
|
||||||
label Start
|
label Start
|
||||||
xywh {380 625 88 25}
|
xywh {535 630 88 25}
|
||||||
}
|
}
|
||||||
Fl_Button kill_button {
|
Fl_Button kill_button {
|
||||||
label Kill
|
label Kill
|
||||||
|
@ -39,7 +39,7 @@ The program will be started with its current directory being a uniquely named di
|
||||||
}
|
}
|
||||||
Fl_Choice save_signal_choice {
|
Fl_Choice save_signal_choice {
|
||||||
label {Save Signal:} open
|
label {Save Signal:} open
|
||||||
xywh {110 625 170 25} down_box BORDER_BOX
|
xywh {110 468 170 25} down_box BORDER_BOX
|
||||||
} {
|
} {
|
||||||
MenuItem {} {
|
MenuItem {} {
|
||||||
label None
|
label None
|
||||||
|
@ -59,12 +59,41 @@ The program will be started with its current directory being a uniquely named di
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Fl_Box {} {
|
Fl_Box {} {
|
||||||
label {The environment variables $NSM_CLIENT_ID and $NSM_SESSION_NAME will contain the unique client ID (suitable for use as e.g. a JACK client name) and the display name for the session, respectively.}
|
label {The environment variables $NSM_CLIENT_ID and $NSM_SESSION_NAME will contain the unique client ID (suitable for use as e.g. a JACK client name) and the display name for the session, respectively. The variable $CONFIG_FILE will contain the name of the config file selected above.}
|
||||||
xywh {15 312 460 87} box BORDER_BOX color 41 labelfont 8 labelcolor 55 align 128
|
xywh {15 235 610 69} box BORDER_BOX color 41 labelfont 8 labelsize 12 labelcolor 55 align 128
|
||||||
}
|
}
|
||||||
Fl_Box {} {
|
Fl_Box {} {
|
||||||
label {Some (very few) programs may respond to a specific Unix signal by somehow saving their state. If 'Save Signal' is set to something other than 'None', then NSM Proxy will deliver the specified signal to the proxied process upon an NSM 'Save' event. Most programs will treat these signals just like SIGTERM and die. You have been warned.}
|
label {Some (very few) programs may respond to a specific Unix signal by somehow saving their state. If 'Save Signal' is set to something other than 'None', then NSM Proxy will deliver the specified signal to the proxied process upon an NSM 'Save' event. Most programs will treat these signals just like SIGTERM and die. You have been warned.}
|
||||||
xywh {15 497 460 114} box BORDER_BOX color 41 labelfont 8 labelcolor 55 align 128
|
xywh {15 378 610 79} box BORDER_BOX color 41 labelfont 8 labelsize 12 labelcolor 55 align 128
|
||||||
|
}
|
||||||
|
Fl_Choice stop_signal_choice {
|
||||||
|
label {Stop Signal:} open
|
||||||
|
xywh {108 592 170 25} down_box BORDER_BOX
|
||||||
|
} {
|
||||||
|
MenuItem {} {
|
||||||
|
label SIGTERM
|
||||||
|
xywh {10 10 40 24}
|
||||||
|
}
|
||||||
|
MenuItem {} {
|
||||||
|
label SIGINT
|
||||||
|
xywh {40 40 40 24}
|
||||||
|
}
|
||||||
|
MenuItem {} {
|
||||||
|
label SIGHUP
|
||||||
|
xywh {50 50 40 24}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Fl_Box {} {
|
||||||
|
label {Most programs will shutdown gracefully when sent a SIGTERM or SIGINT signal. It's impossible to know which signal a specific program will respond to. A unhandled signal will simply kill the process, and may cause problems with the audio subsystem (e.g. JACK). Check the program's documentation or source code to determine which signal to use to stop it gracefully.}
|
||||||
|
xywh {15 502 610 79} box BORDER_BOX color 41 labelfont 8 labelsize 12 labelcolor 55 align 128
|
||||||
|
}
|
||||||
|
Fl_File_Input config_file_input {
|
||||||
|
label {Config File:}
|
||||||
|
xywh {114 195 406 31}
|
||||||
|
}
|
||||||
|
Fl_Button config_file_browse_button {
|
||||||
|
label Browse
|
||||||
|
xywh {530 195 85 25}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
#define APP_NAME "NSM Proxy"
|
#define APP_NAME "NSM Proxy"
|
||||||
#define APP_TITLE "NSM Proxy"
|
#define APP_TITLE "NSM Proxy"
|
||||||
|
|
||||||
|
#include <FL/Fl_File_Chooser.H>
|
||||||
#include "NSM_Proxy_UI.H"
|
#include "NSM_Proxy_UI.H"
|
||||||
#include <lo/lo.h>
|
#include <lo/lo.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
@ -50,6 +51,8 @@ osc_update ( const char *path, const char *types, lo_arg **argv, int argc, lo_me
|
||||||
ui->arguments_input->value( &argv[0]->s );
|
ui->arguments_input->value( &argv[0]->s );
|
||||||
else if (!strcmp( path, "/nsm/proxy/executable" ))
|
else if (!strcmp( path, "/nsm/proxy/executable" ))
|
||||||
ui->executable_input->value( &argv[0]->s );
|
ui->executable_input->value( &argv[0]->s );
|
||||||
|
else if (!strcmp( path, "/nsm/proxy/config_file" ))
|
||||||
|
ui->config_file_input->value( &argv[0]->s );
|
||||||
else if (!strcmp( path, "/nsm/proxy/save_signal" ))
|
else if (!strcmp( path, "/nsm/proxy/save_signal" ))
|
||||||
{
|
{
|
||||||
if ( argv[0]->i == SIGUSR1 )
|
if ( argv[0]->i == SIGUSR1 )
|
||||||
|
@ -61,6 +64,15 @@ osc_update ( const char *path, const char *types, lo_arg **argv, int argc, lo_me
|
||||||
else
|
else
|
||||||
ui->save_signal_choice->value( 0 );
|
ui->save_signal_choice->value( 0 );
|
||||||
}
|
}
|
||||||
|
else if (!strcmp( path, "/nsm/proxy/stop_signal" ))
|
||||||
|
{
|
||||||
|
if ( argv[0]->i == SIGTERM )
|
||||||
|
ui->stop_signal_choice->value( 0 );
|
||||||
|
else if ( argv[0]->i == SIGINT )
|
||||||
|
ui->stop_signal_choice->value( 1 );
|
||||||
|
else if ( argv[0]->i == SIGHUP )
|
||||||
|
ui->stop_signal_choice->value( 2 );
|
||||||
|
}
|
||||||
|
|
||||||
Fl::unlock();
|
Fl::unlock();
|
||||||
|
|
||||||
|
@ -85,8 +97,10 @@ init_osc ( const char *osc_port )
|
||||||
|
|
||||||
lo_server_thread_add_method( loth, "/nsm/proxy/executable", "s", osc_update, NULL );
|
lo_server_thread_add_method( loth, "/nsm/proxy/executable", "s", osc_update, NULL );
|
||||||
lo_server_thread_add_method( loth, "/nsm/proxy/arguments", "s", osc_update, NULL );
|
lo_server_thread_add_method( loth, "/nsm/proxy/arguments", "s", osc_update, NULL );
|
||||||
|
lo_server_thread_add_method( loth, "/nsm/proxy/config_file", "s", osc_update, NULL );
|
||||||
lo_server_thread_add_method( loth, "/nsm/proxy/label", "s", osc_update, NULL );
|
lo_server_thread_add_method( loth, "/nsm/proxy/label", "s", osc_update, NULL );
|
||||||
lo_server_thread_add_method( loth, "/nsm/proxy/save_signal", "i", osc_update, NULL );
|
lo_server_thread_add_method( loth, "/nsm/proxy/save_signal", "i", osc_update, NULL );
|
||||||
|
lo_server_thread_add_method( loth, "/nsm/proxy/stop_signal", "i", osc_update, NULL );
|
||||||
|
|
||||||
lo_server_thread_start( loth );
|
lo_server_thread_start( loth );
|
||||||
}
|
}
|
||||||
|
@ -104,9 +118,10 @@ handle_kill ( Fl_Widget *o, void *v )
|
||||||
void
|
void
|
||||||
handle_start ( Fl_Widget *o, void *v )
|
handle_start ( Fl_Widget *o, void *v )
|
||||||
{
|
{
|
||||||
lo_send_from( nsmp_addr, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/start", "ss",
|
lo_send_from( nsmp_addr, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/start", "sss",
|
||||||
ui->executable_input->value(),
|
ui->executable_input->value(),
|
||||||
ui->arguments_input->value() );
|
ui->arguments_input->value(),
|
||||||
|
ui->config_file_input->value() );
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -122,6 +137,20 @@ handle_executable ( Fl_Widget *o, void *v )
|
||||||
ui->label_input->value( ui->executable_input->value() );
|
ui->label_input->value( ui->executable_input->value() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
handle_config_file ( Fl_Widget *o, void *v )
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
handle_config_file_browse ( Fl_Widget *o, void *v )
|
||||||
|
{
|
||||||
|
const char * file = fl_file_chooser( "Pick file", "*", NULL, 1 );
|
||||||
|
|
||||||
|
ui->config_file_input->value( file );
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
handle_save_signal ( Fl_Widget *o, void *v )
|
handle_save_signal ( Fl_Widget *o, void *v )
|
||||||
{
|
{
|
||||||
|
@ -140,14 +169,35 @@ handle_save_signal ( Fl_Widget *o, void *v )
|
||||||
sig );
|
sig );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
handle_stop_signal ( Fl_Widget *o, void *v )
|
||||||
|
{
|
||||||
|
int sig = SIGTERM;
|
||||||
|
|
||||||
|
const char* picked = ui->stop_signal_choice->mvalue()->label();
|
||||||
|
|
||||||
|
if ( !strcmp( picked, "SIGTERM" ) )
|
||||||
|
sig = SIGTERM;
|
||||||
|
else if ( !strcmp( picked, "SIGINT" ) )
|
||||||
|
sig = SIGINT;
|
||||||
|
else if ( !strcmp( picked, "SIGHUP" ) )
|
||||||
|
sig = SIGHUP;
|
||||||
|
|
||||||
|
lo_send_from( nsmp_addr, losrv, LO_TT_IMMEDIATE,"/nsm/proxy/stop_signal", "i",
|
||||||
|
sig );
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
connect_ui ( void )
|
connect_ui ( void )
|
||||||
{
|
{
|
||||||
ui->executable_input->callback( handle_executable, NULL );
|
ui->executable_input->callback( handle_executable, NULL );
|
||||||
|
ui->config_file_input->callback( handle_config_file, NULL );
|
||||||
ui->kill_button->callback( handle_kill, NULL );
|
ui->kill_button->callback( handle_kill, NULL );
|
||||||
ui->start_button->callback( handle_start, NULL );
|
ui->start_button->callback( handle_start, NULL );
|
||||||
ui->save_signal_choice->callback( handle_save_signal, NULL );
|
ui->save_signal_choice->callback( handle_save_signal, NULL );
|
||||||
|
ui->stop_signal_choice->callback( handle_stop_signal, NULL );
|
||||||
ui->label_input->callback( handle_label, NULL );
|
ui->label_input->callback( handle_label, NULL );
|
||||||
|
ui->config_file_browse_button->callback( handle_config_file_browse, NULL );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -56,16 +56,19 @@ class NSM_Proxy {
|
||||||
|
|
||||||
char *_label;
|
char *_label;
|
||||||
char *_executable;
|
char *_executable;
|
||||||
|
char *_config_file;
|
||||||
char *_arguments;
|
char *_arguments;
|
||||||
int _save_signal;
|
int _save_signal;
|
||||||
|
int _stop_signal;
|
||||||
int _pid;
|
int _pid;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
NSM_Proxy ( )
|
NSM_Proxy ( )
|
||||||
{
|
{
|
||||||
_label = _executable = _arguments = 0;
|
_label = _executable = _arguments = _config_file = 0;
|
||||||
_save_signal = 0;
|
_save_signal = 0;
|
||||||
|
_stop_signal = SIGTERM;
|
||||||
_pid = 0;
|
_pid = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,15 +79,17 @@ public:
|
||||||
void kill ( void )
|
void kill ( void )
|
||||||
{
|
{
|
||||||
if ( _pid )
|
if ( _pid )
|
||||||
::kill( _pid, SIGTERM );
|
::kill( _pid, _stop_signal );
|
||||||
}
|
}
|
||||||
|
|
||||||
bool start ( const char *executable, const char *arguments )
|
bool start ( const char *executable, const char *arguments, const char *config_file )
|
||||||
{
|
{
|
||||||
if ( _executable )
|
if ( _executable )
|
||||||
free( _executable );
|
free( _executable );
|
||||||
if ( _arguments )
|
if ( _arguments )
|
||||||
free( _arguments );
|
free( _arguments );
|
||||||
|
if ( _config_file )
|
||||||
|
free( _config_file );
|
||||||
|
|
||||||
_executable = strdup( executable );
|
_executable = strdup( executable );
|
||||||
|
|
||||||
|
@ -93,6 +98,11 @@ public:
|
||||||
else
|
else
|
||||||
_arguments = NULL;
|
_arguments = NULL;
|
||||||
|
|
||||||
|
if ( config_file )
|
||||||
|
_config_file = strdup( config_file );
|
||||||
|
else
|
||||||
|
_config_file = NULL;
|
||||||
|
|
||||||
return start();
|
return start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,6 +138,8 @@ public:
|
||||||
|
|
||||||
setenv( "NSM_CLIENT_ID", nsm_client_id, 1 );
|
setenv( "NSM_CLIENT_ID", nsm_client_id, 1 );
|
||||||
setenv( "NSM_SESSION_NAME", nsm_display_name, 1 );
|
setenv( "NSM_SESSION_NAME", nsm_display_name, 1 );
|
||||||
|
if ( _config_file )
|
||||||
|
setenv( "CONFIG_FILE", _config_file, 1 );
|
||||||
unsetenv( "NSM_URL" );
|
unsetenv( "NSM_URL" );
|
||||||
|
|
||||||
if ( -1 == execvp( "/bin/sh", args ) )
|
if ( -1 == execvp( "/bin/sh", args ) )
|
||||||
|
@ -147,6 +159,11 @@ public:
|
||||||
{
|
{
|
||||||
_save_signal = s;
|
_save_signal = s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void stop_signal ( int s )
|
||||||
|
{
|
||||||
|
_stop_signal = s;
|
||||||
|
}
|
||||||
|
|
||||||
void label ( const char *s )
|
void label ( const char *s )
|
||||||
{
|
{
|
||||||
|
@ -187,7 +204,12 @@ public:
|
||||||
if ( _arguments && strlen(_arguments) )
|
if ( _arguments && strlen(_arguments) )
|
||||||
fprintf( fp, "arguments\n\t%s\n", _arguments );
|
fprintf( fp, "arguments\n\t%s\n", _arguments );
|
||||||
|
|
||||||
|
if ( _config_file && strlen(_config_file) )
|
||||||
|
fprintf( fp, "config file\n\t%s\n", _config_file );
|
||||||
|
|
||||||
fprintf( fp, "save signal\n\t%i\n", _save_signal );
|
fprintf( fp, "save signal\n\t%i\n", _save_signal );
|
||||||
|
|
||||||
|
fprintf( fp, "stop signal\n\t%i\n", _stop_signal );
|
||||||
|
|
||||||
if ( _label && strlen(_label) )
|
if ( _label && strlen(_label) )
|
||||||
fprintf( fp, "label\n\t%s\n", _label );
|
fprintf( fp, "label\n\t%s\n", _label );
|
||||||
|
@ -221,11 +243,18 @@ public:
|
||||||
_executable = value;
|
_executable = value;
|
||||||
else if (!strcmp( name, "arguments" ) )
|
else if (!strcmp( name, "arguments" ) )
|
||||||
_arguments = value;
|
_arguments = value;
|
||||||
|
else if (!strcmp( name, "config file" ) )
|
||||||
|
_config_file = value;
|
||||||
else if ( !strcmp( name, "save signal" ) )
|
else if ( !strcmp( name, "save signal" ) )
|
||||||
{
|
{
|
||||||
_save_signal = atoi( value );
|
_save_signal = atoi( value );
|
||||||
free( value );
|
free( value );
|
||||||
}
|
}
|
||||||
|
else if ( !strcmp( name, "stop signal" ) )
|
||||||
|
{
|
||||||
|
_stop_signal = atoi( value );
|
||||||
|
free( value );
|
||||||
|
}
|
||||||
else if ( !strcmp( name, "label" ) )
|
else if ( !strcmp( name, "label" ) )
|
||||||
{
|
{
|
||||||
label( value );
|
label( value );
|
||||||
|
@ -254,6 +283,8 @@ public:
|
||||||
lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/label", "s", _label ? _label : "" );
|
lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/label", "s", _label ? _label : "" );
|
||||||
lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/executable", "s", _executable ? _executable : "" );
|
lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/executable", "s", _executable ? _executable : "" );
|
||||||
lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/arguments", "s", _arguments ? _arguments : "" );
|
lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/arguments", "s", _arguments ? _arguments : "" );
|
||||||
|
lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/config_file", "s", _config_file ? _config_file : "" );
|
||||||
|
lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/stop_signal", "i", _stop_signal );
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -493,12 +524,20 @@ osc_save_signal ( const char *path, const char *types, lo_arg **argv, int argc,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
osc_stop_signal ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
|
||||||
|
{
|
||||||
|
nsm_proxy->stop_signal( argv[0]->i );
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
osc_start ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
|
osc_start ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
|
||||||
{
|
{
|
||||||
snapshot( project_file );
|
snapshot( project_file );
|
||||||
|
|
||||||
if ( nsm_proxy->start( &argv[0]->s, &argv[1]->s ) )
|
if ( nsm_proxy->start( &argv[0]->s, &argv[1]->s, &argv[2]->s ) )
|
||||||
{
|
{
|
||||||
hide_gui();
|
hide_gui();
|
||||||
}
|
}
|
||||||
|
@ -567,8 +606,9 @@ init_osc ( const char *osc_port )
|
||||||
/* GUI */
|
/* GUI */
|
||||||
lo_server_add_method( losrv, "/nsm/proxy/label", "s", osc_label, NULL );
|
lo_server_add_method( losrv, "/nsm/proxy/label", "s", osc_label, NULL );
|
||||||
lo_server_add_method( losrv, "/nsm/proxy/save_signal", "i", osc_save_signal, NULL );
|
lo_server_add_method( losrv, "/nsm/proxy/save_signal", "i", osc_save_signal, NULL );
|
||||||
|
lo_server_add_method( losrv, "/nsm/proxy/stop_signal", "i", osc_stop_signal, NULL );
|
||||||
lo_server_add_method( losrv, "/nsm/proxy/kill", "", osc_kill, NULL );
|
lo_server_add_method( losrv, "/nsm/proxy/kill", "", osc_kill, NULL );
|
||||||
lo_server_add_method( losrv, "/nsm/proxy/start", "ss", osc_start, NULL );
|
lo_server_add_method( losrv, "/nsm/proxy/start", "sss", osc_start, NULL );
|
||||||
lo_server_add_method( losrv, "/nsm/proxy/update", "", osc_update, NULL );
|
lo_server_add_method( losrv, "/nsm/proxy/update", "", osc_update, NULL );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue