ladish/daemon/studio.c

757 lines
17 KiB
C
Raw Normal View History

/* -*- Mode: C ; c-basic-offset: 2 -*- */
/*
* LADI Session Handler (ladish)
*
* Copyright (C) 2009 Nedko Arnaudov <nedko@arnaudov.name>
*
**************************************************************************
* This file contains part of the studio singleton object implementation
* Other parts are in the other studio*.c files in same directory.
**************************************************************************
*
* LADI Session Handler 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.
*
* LADI Session Handler 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 LADI Session Handler. If not, see <http://www.gnu.org/licenses/>
* or write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "common.h"
2009-08-24 01:04:07 +03:00
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
2009-08-24 01:58:14 +03:00
#include <dirent.h>
2009-08-24 01:04:07 +03:00
#include "studio_internal.h"
2009-08-22 03:07:19 +03:00
#include "../dbus_constants.h"
#include "control.h"
#include "../catdup.h"
#include "dirhelpers.h"
#include "graph_dict.h"
2009-09-12 11:11:52 +03:00
#include "escape.h"
2009-08-24 01:58:14 +03:00
#define STUDIOS_DIR "/studios/"
char * g_studios_dir;
struct studio g_studio;
#define EVENT_JACK_START 0
#define EVENT_JACK_STOP 1
struct event
{
struct list_head siblings;
unsigned int type;
};
bool studio_name_generate(char ** name_ptr)
{
time_t now;
char timestamp_str[26];
char * name;
time(&now);
//ctime_r(&now, timestamp_str);
//timestamp_str[24] = 0;
snprintf(timestamp_str, sizeof(timestamp_str), "%llu", (unsigned long long)now);
name = catdup("Studio ", timestamp_str);
if (name == NULL)
{
lash_error("catdup failed to create studio name");
return false;
}
*name_ptr = name;
return true;
}
bool
studio_publish(void)
{
dbus_object_path object;
assert(g_studio.name != NULL);
2009-09-06 00:13:05 +03:00
object = dbus_object_path_new(
STUDIO_OBJECT_PATH,
&g_interface_studio, &g_studio,
&g_interface_patchbay, ladish_graph_get_dbus_context(g_studio.studio_graph),
&g_iface_graph_dict, g_studio.studio_graph,
2009-09-06 00:13:05 +03:00
NULL);
if (object == NULL)
{
2009-09-04 02:27:05 +03:00
lash_error("dbus_object_path_new() failed");
return false;
}
2009-09-04 02:27:05 +03:00
if (!dbus_object_path_register(g_dbus_connection, object))
{
lash_error("object_path_register() failed");
2009-09-04 02:27:05 +03:00
dbus_object_path_destroy(g_dbus_connection, object);
return false;
}
lash_info("Studio D-Bus object created. \"%s\"", g_studio.name);
g_studio.dbus_object = object;
emit_studio_appeared();
return true;
}
2009-08-30 22:40:28 +03:00
static void emit_studio_started()
{
2009-09-05 19:12:53 +03:00
dbus_signal_emit(g_dbus_connection, STUDIO_OBJECT_PATH, IFACE_STUDIO, "StudioStarted", "");
2009-08-30 22:40:28 +03:00
}
2009-08-30 22:40:28 +03:00
static void emit_studio_stopped()
{
2009-09-05 19:12:53 +03:00
dbus_signal_emit(g_dbus_connection, STUDIO_OBJECT_PATH, IFACE_STUDIO, "StudioStopped", "");
2009-08-30 22:40:28 +03:00
}
bool studio_start(void)
2009-08-30 22:40:28 +03:00
{
if (!g_studio.jack_running)
{
2009-08-30 22:40:28 +03:00
lash_info("Starting JACK server.");
if (!jack_proxy_start_server())
{
lash_error("jack_proxy_start_server() failed.");
return false;
}
}
2009-08-30 22:40:28 +03:00
emit_studio_started();
2009-08-30 22:40:28 +03:00
return true;
}
static bool studio_stop(void)
{
if (g_studio.jack_running)
{
lash_info("Stopping JACK server...");
g_studio.automatic = false; /* even if it was automatic, it is not anymore because user knows about it */
if (jack_proxy_stop_server())
{
g_studio.jack_running = false;
2009-08-30 22:40:28 +03:00
emit_studio_stopped();
}
else
{
lash_error("Stopping JACK server failed.");
2009-08-30 22:40:28 +03:00
return false;
}
}
2009-08-30 22:40:28 +03:00
return true;
}
void
studio_clear(void)
{
jack_conf_clear();
studio_stop();
2009-09-06 00:13:05 +03:00
2009-08-30 22:40:28 +03:00
g_studio.modified = false;
g_studio.persisted = false;
g_studio.automatic = false;
2009-08-30 22:40:28 +03:00
if (g_studio.dbus_object != NULL)
{
2009-09-04 02:27:05 +03:00
dbus_object_path_destroy(g_dbus_connection, g_studio.dbus_object);
g_studio.dbus_object = NULL;
emit_studio_disappeared();
}
if (g_studio.name != NULL)
{
free(g_studio.name);
g_studio.name = NULL;
}
if (g_studio.filename != NULL)
{
free(g_studio.filename);
g_studio.filename = NULL;
}
}
void
studio_clear_if_automatic(void)
{
if (g_studio.automatic)
{
lash_info("Unloading automatic studio.");
studio_clear();
return;
}
}
void on_event_jack_started(void)
{
if (g_studio.dbus_object == NULL)
{
assert(g_studio.name == NULL);
if (!studio_name_generate(&g_studio.name))
{
lash_error("studio_name_generate() failed.");
return;
}
g_studio.automatic = true;
studio_publish();
}
if (!studio_fetch_jack_settings())
{
lash_error("studio_fetch_jack_settings() failed.");
studio_clear_if_automatic();
return;
}
lash_info("jack conf successfully retrieved");
g_studio.jack_conf_valid = true;
g_studio.jack_running = true;
2009-09-03 05:45:39 +03:00
if (!graph_proxy_create(JACKDBUS_SERVICE_NAME, JACKDBUS_OBJECT_PATH, false, &g_studio.jack_graph_proxy))
2009-09-03 05:45:39 +03:00
{
2009-09-06 00:13:05 +03:00
lash_error("graph_proxy_create() failed for jackdbus");
}
else
{
if (!ladish_jack_dispatcher_create(g_studio.jack_graph_proxy, g_studio.jack_graph, g_studio.studio_graph, &g_studio.jack_dispatcher))
2009-09-06 01:15:25 +03:00
{
lash_error("ladish_jack_dispatcher_create() failed.");
}
2009-09-06 00:13:05 +03:00
2009-09-06 01:15:25 +03:00
if (!graph_proxy_activate(g_studio.jack_graph_proxy))
{
lash_error("graph_proxy_activate() failed.");
}
2009-09-03 05:45:39 +03:00
}
}
void on_event_jack_stopped(void)
{
studio_clear_if_automatic();
g_studio.jack_running = false;
2009-09-06 01:15:25 +03:00
if (g_studio.jack_dispatcher)
{
ladish_jack_dispatcher_destroy(g_studio.jack_dispatcher);
g_studio.jack_dispatcher = NULL;
2009-09-06 01:15:25 +03:00
}
ladish_graph_clear(g_studio.jack_graph);
ladish_graph_clear(g_studio.studio_graph);
2009-09-06 01:15:25 +03:00
if (g_studio.jack_graph_proxy)
{
graph_proxy_destroy(g_studio.jack_graph_proxy);
g_studio.jack_graph_proxy = NULL;
}
2009-09-03 05:45:39 +03:00
/* TODO: if user wants, restart jack server and reconnect all jack apps to it */
}
void studio_run(void)
{
struct event * event_ptr;
while (!list_empty(&g_studio.event_queue))
{
event_ptr = list_entry(g_studio.event_queue.next, struct event, siblings);
list_del(g_studio.event_queue.next);
switch (event_ptr->type)
{
case EVENT_JACK_START:
on_event_jack_started();
break;
case EVENT_JACK_STOP:
on_event_jack_stopped();
break;
}
free(event_ptr);
}
}
static void on_jack_server_started(void)
{
struct event * event_ptr;
lash_info("JACK server start detected.");
event_ptr = malloc(sizeof(struct event));
if (event_ptr == NULL)
{
lash_error("malloc() failed to allocate struct event. Ignoring JACK start.");
return;
}
event_ptr->type = EVENT_JACK_START;
list_add_tail(&event_ptr->siblings, &g_studio.event_queue);
}
static void on_jack_server_stopped(void)
{
struct event * event_ptr;
lash_info("JACK server stop detected.");
event_ptr = malloc(sizeof(struct event));
if (event_ptr == NULL)
{
lash_error("malloc() failed to allocate struct event. Ignoring JACK stop.");
return;
}
event_ptr->type = EVENT_JACK_STOP;
list_add_tail(&event_ptr->siblings, &g_studio.event_queue);
}
static void on_jack_server_appeared(void)
{
lash_info("JACK controller appeared.");
}
static void on_jack_server_disappeared(void)
{
lash_info("JACK controller disappeared.");
}
bool studio_init(void)
{
lash_info("studio object construct");
g_studios_dir = catdup(g_base_dir, STUDIOS_DIR);
if (g_studios_dir == NULL)
{
lash_error("catdup failed for '%s' and '%s'", g_base_dir, STUDIOS_DIR);
2009-09-06 00:13:05 +03:00
goto fail;
}
if (!ensure_dir_exist(g_studios_dir, 0700))
{
2009-09-06 00:13:05 +03:00
goto free_studios_dir;
}
INIT_LIST_HEAD(&g_studio.all_connections);
INIT_LIST_HEAD(&g_studio.all_ports);
INIT_LIST_HEAD(&g_studio.all_clients);
INIT_LIST_HEAD(&g_studio.jack_connections);
INIT_LIST_HEAD(&g_studio.jack_ports);
INIT_LIST_HEAD(&g_studio.jack_clients);
INIT_LIST_HEAD(&g_studio.rooms);
INIT_LIST_HEAD(&g_studio.clients);
INIT_LIST_HEAD(&g_studio.ports);
INIT_LIST_HEAD(&g_studio.jack_conf);
INIT_LIST_HEAD(&g_studio.jack_params);
INIT_LIST_HEAD(&g_studio.event_queue);
g_studio.dbus_object = NULL;
g_studio.name = NULL;
g_studio.filename = NULL;
g_studio.jack_running = false;
2009-09-06 00:13:05 +03:00
//studio_clear();
if (!ladish_graph_create(&g_studio.jack_graph, STUDIO_OBJECT_PATH))
2009-09-06 00:13:05 +03:00
{
lash_error("ladish_graph_create() failed to create jack graph object.");
2009-09-06 00:13:05 +03:00
goto free_studios_dir;
}
if (!ladish_graph_create(&g_studio.studio_graph, NULL))
{
lash_error("ladish_graph_create() failed to create studio graph object.");
goto jack_graph_destroy;
}
if (!jack_proxy_init(
on_jack_server_started,
on_jack_server_stopped,
on_jack_server_appeared,
on_jack_server_disappeared))
{
lash_error("jack_proxy_init() failed.");
goto studio_graph_destroy;
}
return true;
2009-09-06 00:13:05 +03:00
studio_graph_destroy:
ladish_graph_destroy(g_studio.studio_graph);
jack_graph_destroy:
ladish_graph_destroy(g_studio.jack_graph);
2009-09-06 00:13:05 +03:00
free_studios_dir:
studio_clear();
free(g_studios_dir);
fail:
return false;
}
void studio_uninit(void)
{
jack_proxy_uninit();
ladish_graph_destroy(g_studio.studio_graph);
studio_clear();
2009-09-03 05:42:35 +03:00
free(g_studios_dir);
lash_info("studio object destroy");
}
bool studio_is_loaded(void)
{
return g_studio.dbus_object != NULL;
}
bool studio_compose_filename(const char * name, char ** filename_ptr_ptr, char ** backup_filename_ptr_ptr)
{
size_t len_dir;
char * p;
const char * src;
char * filename_ptr;
char * backup_filename_ptr;
len_dir = strlen(g_studios_dir);
filename_ptr = malloc(len_dir + 1 + strlen(name) * 3 + 4 + 1);
if (filename_ptr == NULL)
{
lash_error("malloc failed to allocate memory for studio file path");
return false;
}
2009-08-31 04:19:50 +03:00
if (backup_filename_ptr_ptr != NULL)
{
2009-08-31 04:19:50 +03:00
backup_filename_ptr = malloc(len_dir + 1 + strlen(name) * 3 + 4 + 4 + 1);
if (backup_filename_ptr == NULL)
{
lash_error("malloc failed to allocate memory for studio backup file path");
free(filename_ptr);
return false;
}
}
p = filename_ptr;
memcpy(p, g_studios_dir, len_dir);
p += len_dir;
*p++ = '/';
src = name;
escape(&src, &p);
strcpy(p, ".xml");
*filename_ptr_ptr = filename_ptr;
2009-08-31 04:19:50 +03:00
if (backup_filename_ptr_ptr != NULL)
{
strcpy(backup_filename_ptr, filename_ptr);
strcat(backup_filename_ptr, ".bak");
*backup_filename_ptr_ptr = backup_filename_ptr;
}
return true;
}
2009-08-24 01:58:14 +03:00
bool studios_iterate(void * call_ptr, void * context, bool (* callback)(void * call_ptr, void * context, const char * studio, uint32_t modtime))
{
DIR * dir;
struct dirent * dentry;
size_t len;
struct stat st;
char * path;
2009-08-31 04:19:50 +03:00
char * name;
2009-08-24 01:58:14 +03:00
dir = opendir(g_studios_dir);
if (dir == NULL)
{
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "Cannot open directory '%s': %d (%s)", g_studios_dir, errno, strerror(errno));
return false;
}
while ((dentry = readdir(dir)) != NULL)
{
if (dentry->d_type != DT_REG)
continue;
len = strlen(dentry->d_name);
2009-08-31 04:19:50 +03:00
if (len <= 4 || strcmp(dentry->d_name + (len - 4), ".xml") != 0)
2009-08-24 01:58:14 +03:00
continue;
path = catdup(g_studios_dir, dentry->d_name);
if (path == NULL)
{
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "catdup() failed");
return false;
}
if (stat(path, &st) != 0)
{
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "failed to stat '%s': %d (%s)", path, errno, strerror(errno));
free(path);
return false;
}
free(path);
2009-08-31 04:19:50 +03:00
name = malloc(len - 4 + 1);
name[unescape(dentry->d_name, len - 4, name)] = 0;
//lash_info("name = '%s'", name);
if (!callback(call_ptr, context, name, st.st_mtime))
2009-08-24 01:58:14 +03:00
{
2009-08-31 04:19:50 +03:00
free(name);
2009-08-24 01:58:14 +03:00
closedir(dir);
return false;
}
2009-08-31 04:19:50 +03:00
free(name);
2009-08-24 01:58:14 +03:00
}
closedir(dir);
return true;
}
2009-08-30 14:36:27 +03:00
bool studio_delete(void * call_ptr, const char * studio_name)
{
char * filename;
char * bak_filename;
struct stat st;
bool ret;
ret = false;
if (!studio_compose_filename(studio_name, &filename, &bak_filename))
2009-08-30 14:36:27 +03:00
{
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "failed to compose studio filename");
goto exit;
}
2009-08-30 17:16:21 +03:00
lash_info("Deleting studio ('%s')", filename);
2009-08-30 14:36:27 +03:00
if (unlink(filename) != 0)
{
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "unlink(%s) failed: %d (%s)", filename, errno, strerror(errno));
goto free;
}
/* try to delete the backup file */
if (stat(bak_filename, &st) == 0)
{
if (unlink(bak_filename) != 0)
{
/* failing to delete backup file will not case delete command failure */
lash_error("unlink(%s) failed: %d (%s)", bak_filename, errno, strerror(errno));
}
}
ret = true;
free:
free(filename);
free(bak_filename);
exit:
return ret;
}
2009-08-23 12:58:01 +03:00
void emit_studio_renamed()
{
2009-09-05 19:12:53 +03:00
dbus_signal_emit(g_dbus_connection, STUDIO_OBJECT_PATH, IFACE_STUDIO, "StudioRenamed", "s", &g_studio.name);
2009-08-23 12:58:01 +03:00
}
static void ladish_get_studio_name(struct dbus_method_call * call_ptr)
{
method_return_new_single(call_ptr, DBUS_TYPE_STRING, &g_studio.name);
}
static void ladish_rename_studio(struct dbus_method_call * call_ptr)
{
const char * new_name;
char * new_name_dup;
if (!dbus_message_get_args(call_ptr->message, &g_dbus_error, DBUS_TYPE_STRING, &new_name, DBUS_TYPE_INVALID))
{
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_INVALID_ARGS, "Invalid arguments to method \"%s\": %s", call_ptr->method_name, g_dbus_error.message);
dbus_error_free(&g_dbus_error);
return;
}
2009-08-31 01:50:24 +03:00
lash_info("Rename studio request (%s)", new_name);
new_name_dup = strdup(new_name);
if (new_name_dup == NULL)
{
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "strdup() failed to allocate new name.");
return;
}
free(g_studio.name);
g_studio.name = new_name_dup;
method_return_new_void(call_ptr);
2009-08-23 12:58:01 +03:00
emit_studio_renamed();
}
static void ladish_save_studio(struct dbus_method_call * call_ptr)
{
2009-08-24 01:04:07 +03:00
if (studio_save(call_ptr))
{
method_return_new_void(call_ptr);
}
}
2009-08-20 22:43:11 +03:00
static void ladish_unload_studio(struct dbus_method_call * call_ptr)
2009-08-30 16:23:19 +03:00
{
2009-08-31 01:50:24 +03:00
lash_info("Unload studio request");
2009-08-30 16:23:19 +03:00
studio_clear();
method_return_new_void(call_ptr);
}
2009-08-30 16:13:38 +03:00
bool studio_new(void * call_ptr, const char * studio_name)
{
2009-08-31 01:50:24 +03:00
lash_info("New studio request (%s)", studio_name);
2009-08-30 16:13:38 +03:00
studio_clear();
assert(g_studio.name == NULL);
if (*studio_name != 0)
{
g_studio.name = strdup(studio_name);
if (g_studio.name == NULL)
{
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "strdup() failed to allocate studio name.");
return false;
}
}
else if (!studio_name_generate(&g_studio.name))
{
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "studio_name_generate() failed.");
return false;
}
if (!studio_publish())
2009-08-30 16:13:38 +03:00
{
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "studio_publish() failed.");
2009-08-30 16:13:38 +03:00
studio_clear();
return false;
}
return true;
}
static void ladish_stop_studio(struct dbus_method_call * call_ptr)
2009-08-30 22:40:28 +03:00
{
lash_info("Studio stop requested");
if (!studio_stop())
{
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "Failed to stop studio.");
}
else
{
method_return_new_void(call_ptr);
}
}
static void ladish_start_studio(struct dbus_method_call * call_ptr)
2009-08-30 22:40:28 +03:00
{
lash_info("Studio start requested");
if (!studio_start())
{
lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "Failed to start studio.");
}
else
{
method_return_new_void(call_ptr);
}
}
METHOD_ARGS_BEGIN(GetName, "Get studio name")
METHOD_ARG_DESCRIBE_OUT("studio_name", "s", "Name of studio")
METHOD_ARGS_END
METHOD_ARGS_BEGIN(Rename, "Rename studio")
METHOD_ARG_DESCRIBE_IN("studio_name", "s", "New name")
METHOD_ARGS_END
METHOD_ARGS_BEGIN(Save, "Save studio")
METHOD_ARGS_END
2009-08-30 16:23:19 +03:00
METHOD_ARGS_BEGIN(Unload, "Unload studio")
METHOD_ARGS_END
2009-08-30 22:40:28 +03:00
METHOD_ARGS_BEGIN(Start, "Start studio")
METHOD_ARGS_END
METHOD_ARGS_BEGIN(Stop, "Stop studio")
METHOD_ARGS_END
2009-08-20 22:43:11 +03:00
METHODS_BEGIN
METHOD_DESCRIBE(GetName, ladish_get_studio_name)
METHOD_DESCRIBE(Rename, ladish_rename_studio)
METHOD_DESCRIBE(Save, ladish_save_studio)
2009-08-30 16:23:19 +03:00
METHOD_DESCRIBE(Unload, ladish_unload_studio)
2009-08-30 22:40:28 +03:00
METHOD_DESCRIBE(Start, ladish_start_studio)
METHOD_DESCRIBE(Stop, ladish_stop_studio)
2009-08-20 22:43:11 +03:00
METHODS_END
2009-08-23 12:58:01 +03:00
SIGNAL_ARGS_BEGIN(StudioRenamed, "Studio name changed")
SIGNAL_ARG_DESCRIBE("studio_name", "s", "New studio name")
SIGNAL_ARGS_END
2009-08-30 22:40:28 +03:00
SIGNAL_ARGS_BEGIN(StudioStarted, "Studio started")
SIGNAL_ARGS_END
SIGNAL_ARGS_BEGIN(StudioStopped, "Studio stopped")
SIGNAL_ARGS_END
2009-08-20 22:43:11 +03:00
SIGNAL_ARGS_BEGIN(RoomAppeared, "Room D-Bus object appeared")
SIGNAL_ARG_DESCRIBE("room_path", "s", "room object path")
SIGNAL_ARGS_END
SIGNAL_ARGS_BEGIN(RoomDisappeared, "Room D-Bus object disappeared")
SIGNAL_ARG_DESCRIBE("room_path", "s", "room object path")
SIGNAL_ARGS_END
SIGNALS_BEGIN
2009-08-23 12:58:01 +03:00
SIGNAL_DESCRIBE(StudioRenamed)
2009-08-30 22:40:28 +03:00
SIGNAL_DESCRIBE(StudioStarted)
SIGNAL_DESCRIBE(StudioStopped)
2009-08-20 22:43:11 +03:00
SIGNAL_DESCRIBE(RoomAppeared)
SIGNAL_DESCRIBE(RoomDisappeared)
SIGNALS_END
2009-08-22 03:07:19 +03:00
INTERFACE_BEGIN(g_interface_studio, IFACE_STUDIO)
2009-08-20 22:43:11 +03:00
INTERFACE_DEFAULT_HANDLER
INTERFACE_EXPOSE_METHODS
INTERFACE_EXPOSE_SIGNALS
INTERFACE_END