/* -*- Mode: C ; c-basic-offset: 2 -*- */ /* * LADI Session Handler (ladish) * * Copyright (C) 2010,2011,2012 Nedko Arnaudov * ************************************************************************** * This file contains implementation save releated helper functions ************************************************************************** * * 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 * or write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include "save.h" #include "escape.h" #include "studio.h" struct ladish_write_vgraph_context { int fd; int indent; ladish_app_supervisor_handle app_supervisor; bool client_visible; }; static bool is_hidden_port_interesting( ladish_app_supervisor_handle app_supervisor, ladish_client_handle UNUSED(client), ladish_port_handle port) { uuid_t app_uuid; ladish_app_handle app; /* hidden ports of external apps should not be saved */ /* hidden ports of stopped managed apps should be saved */ /* hidden ports of started managed apps should not be saved */ if (!ladish_port_get_app(port, app_uuid)) { /* port of external app, don't save */ return false; } app = ladish_app_supervisor_find_app_by_uuid(app_supervisor, app_uuid); if (app == NULL) { ASSERT_NO_PASS; /* this should not happen because app ports are removed before app is removed */ return false; } return !ladish_app_is_running(app); } bool ladish_write_string(int fd, const char * string) { size_t len; ssize_t ret; len = strlen(string); ret = write(fd, string, len); if (ret == -1) { log_error("write(%d, \"%s\", %zu) failed to write file: %d (%s)", fd, string, len, errno, strerror(errno)); return false; } if ((size_t)ret != len) { log_error("write() wrote wrong byte count to file (%zd != %zu).", ret, len); return false; } return true; } bool ladish_write_indented_string(int fd, int indent, const char * string) { ASSERT(indent >= 0); while (indent--) { if (!ladish_write_string(fd, LADISH_XML_BASE_INDENT)) { return false; } } if (!ladish_write_string(fd, string)) { return false; } return true; } bool ladish_write_string_escape_ex(int fd, const char * string, unsigned int flags) { bool ret; char * escaped_buffer; escaped_buffer = malloc(max_escaped_length(strlen(string))); if (escaped_buffer == NULL) { log_error("malloc() failed to allocate buffer for escaped string"); return false; } escape_simple(string, escaped_buffer, flags); ret = ladish_write_string(fd, escaped_buffer); free(escaped_buffer); return ret; } bool ladish_write_string_escape(int fd, const char * string) { return ladish_write_string_escape_ex(fd, string, LADISH_ESCAPE_FLAG_ALL); } static bool ladish_port_dict_ignored_keys_check( void * UNUSED(context), const char * key, const char * UNUSED(value)) { return strcmp(key, URI_A2J_PORT) == 0; } static bool ladish_port_dict_is_empty(ladish_dict_handle dict) { return ladish_dict_iterate(dict, NULL, ladish_port_dict_ignored_keys_check); } #define fd (((struct ladish_write_context *)context)->fd) #define indent (((struct ladish_write_context *)context)->indent) static bool write_dict_entry( void * context, const char * key, const char * value) { if (!ladish_write_indented_string(fd, indent, "")) { return false; } if (!ladish_write_string(fd, value)) { return false; } if (!ladish_write_string(fd, "\n")) { return false; } return true; } static bool ladish_write_room_port( void * context, ladish_port_handle port, const char * name, uint32_t type, uint32_t flags) { uuid_t uuid; char str[37]; bool midi; const char * type_str; bool playback; const char * direction_str; ladish_dict_handle dict; ladish_port_get_uuid(port, uuid); uuid_unparse(uuid, str); playback = (flags & JACKDBUS_PORT_FLAG_INPUT) != 0; ASSERT(playback || (flags & JACKDBUS_PORT_FLAG_OUTPUT) != 0); /* playback or capture */ ASSERT(!(playback && (flags & JACKDBUS_PORT_FLAG_OUTPUT) != 0)); /* but not both */ direction_str = playback ? "playback" : "capture"; midi = type == JACKDBUS_PORT_TYPE_MIDI; ASSERT(midi || type == JACKDBUS_PORT_TYPE_AUDIO); /* midi or audio */ ASSERT(!(midi && type == JACKDBUS_PORT_TYPE_AUDIO)); /* but not both */ type_str = midi ? "midi" : "audio"; log_info("saving room %s %s port '%s' (%s)", direction_str, type_str, name, str); if (!ladish_write_indented_string(fd, indent, "\n")) { return false; } } else { if (!ladish_write_string(fd, "\">\n")) { return false; } if (!ladish_write_dict(fd, indent + 1, dict)) { return false; } if (!ladish_write_indented_string(fd, indent, "\n")) { return false; } } return true; } #undef indent #undef fd bool ladish_write_dict(int fd, int indent, ladish_dict_handle dict) { struct ladish_write_context context; ladish_dict_handle dict_dup; bool ret; if (ladish_dict_get(dict, URI_A2J_PORT) != NULL) { if (!ladish_dict_dup(dict, &dict_dup)) { log_error("ladish_dict_dup() failed"); dict_dup = NULL; } else { ladish_dict_drop(dict_dup, URI_A2J_PORT); dict = dict_dup; } } else { dict_dup = NULL; } if (ladish_dict_is_empty(dict)) { ret = true; goto dup_destroy; } context.fd = fd; context.indent = indent + 1; if (!ladish_write_indented_string(fd, indent, "\n")) { ret = false; goto dup_destroy; } if (!ladish_dict_iterate(dict, &context, write_dict_entry)) { ret = false; goto dup_destroy; } if (!ladish_write_indented_string(fd, indent, "\n")) { ret = false; goto dup_destroy; } ret = true; dup_destroy: if (dict_dup != NULL) { ladish_dict_destroy(dict_dup); } return ret; } bool ladish_write_room_link_ports(int fd, int indent, ladish_room_handle room) { struct ladish_write_context context; ladish_check_integrity(); context.fd = fd; context.indent = indent; if (!ladish_room_iterate_link_ports(room, &context, ladish_write_room_port)) { log_error("ladish_room_iterate_link_ports() failed"); return false; } return true; } /****************/ /* write vgraph */ /****************/ #define fd (((struct ladish_write_vgraph_context *)context)->fd) #define indent (((struct ladish_write_vgraph_context *)context)->indent) #define ctx_ptr ((struct ladish_write_vgraph_context *)context) static bool ladish_save_vgraph_client_begin( void * context, ladish_graph_handle UNUSED(graph), bool hidden, ladish_client_handle client_handle, const char * client_name, void ** UNUSED(client_iteration_context_ptr_ptr)) { uuid_t uuid; char str[37]; uuid_t app_uuid; char app_str[37]; ctx_ptr->client_visible = !hidden; if (!ctx_ptr->client_visible) { if (ladish_client_has_app(client_handle)) { ctx_ptr->client_visible = true; log_info("saving hidden vgraph client '%s' of managed app", client_name); } else { log_info("not saving hidden vgraph client '%s' because it has no app associated", client_name); } } if (!ctx_ptr->client_visible) { return true; } ladish_client_get_uuid(client_handle, uuid); uuid_unparse(uuid, str); log_info("saving vgraph client '%s' (%s)", client_name, str); if (!ladish_write_indented_string(fd, indent, "\n")) { return false; } if (!ladish_write_indented_string(fd, indent + 1, "\n")) { return false; } return true; } static bool ladish_save_vgraph_client_end( void * context, ladish_graph_handle UNUSED(graph), bool UNUSED(hidden), ladish_client_handle client_handle, const char * UNUSED(client_name), void * UNUSED(client_iteration_context_ptr)) { if (!ctx_ptr->client_visible) { return true; } if (!ladish_write_indented_string(fd, indent + 1, "\n")) { return false; } if (!ladish_write_dict(fd, indent + 1, ladish_client_get_dict(client_handle))) { return false; } if (!ladish_write_indented_string(fd, indent, "\n")) { return false; } return true; } static bool ladish_get_vgraph_port_uuids(ladish_graph_handle vgraph, ladish_port_handle port, uuid_t uuid, uuid_t link_uuid) { bool link; if (vgraph != ladish_studio_get_studio_graph()) { link = false; /* room ports are saved using their fixed uuids */ } else { link = ladish_port_is_link(port); if (link) { /* get the generated port uuid that is used for identification in the virtual graph */ ladish_graph_get_port_uuid(vgraph, port, uuid); } } if (!link || link_uuid != NULL) { /* get the real port uuid that is same in both room and studio graphs */ ladish_port_get_uuid(port, link ? link_uuid : uuid); } return link; } static bool ladish_save_vgraph_port( void * context, ladish_graph_handle graph, bool hidden, void * UNUSED(client_iteration_context_ptr), ladish_client_handle client_handle, const char * client_name, ladish_port_handle port_handle, const char * port_name, uint32_t port_type, uint32_t port_flags) { uuid_t uuid; bool link; uuid_t link_uuid; char str[37]; char link_str[37]; ladish_dict_handle dict; if (!ctx_ptr->client_visible) { return true; } /* skip hidden ports of running apps */ if (hidden && !is_hidden_port_interesting(ctx_ptr->app_supervisor, client_handle, port_handle)) { log_info("skippping hidden vgraph port '%s':'%s'", client_name, port_name); return true; } link = ladish_get_vgraph_port_uuids(graph, port_handle, uuid, link_uuid); uuid_unparse(uuid, str); if (link) { uuid_unparse(link_uuid, link_str); log_info("saving vgraph link port '%s':'%s' (%s link=%s)", client_name, port_name, str, link_str); } else { log_info("saving vgraph port '%s':'%s' (%s)", client_name, port_name, str); } if (!ladish_write_indented_string(fd, indent + 2, "\n")) { return false; } } else { if (!ladish_write_string(fd, "\">\n")) { return false; } if (!ladish_write_dict(fd, indent + 3, dict)) { return false; } if (!ladish_write_indented_string(fd, indent + 2, "\n")) { return false; } } return true; } static bool ladish_save_vgraph_connection( void * context, ladish_graph_handle graph, bool connection_hidden, ladish_client_handle client1, ladish_port_handle port1, bool port1_hidden, ladish_client_handle client2, ladish_port_handle port2, bool port2_hidden, ladish_dict_handle dict) { uuid_t uuid; char str[37]; /* if at least one of ports is hidden, connection must be hidden too */ ASSERT(!(port1_hidden || port2_hidden) || connection_hidden); if (connection_hidden && ((port1_hidden && !is_hidden_port_interesting(ctx_ptr->app_supervisor, client1, port1)) || (port2_hidden && !is_hidden_port_interesting(ctx_ptr->app_supervisor, client2, port2)))) { return true; } log_info("saving vgraph connection"); if (!ladish_write_indented_string(fd, indent, "\n")) { return false; } } else { if (!ladish_write_string(fd, "\">\n")) { return false; } if (!ladish_write_dict(fd, indent + 1, dict)) { return false; } if (!ladish_write_indented_string(fd, indent, "\n")) { return false; } } return true; } static bool ladish_save_app( void * context, const char * name, bool running, const char * command, bool terminal, const char * level, pid_t UNUSED(pid), const uuid_t uuid) { const char * unescaped_string; char * escaped_string; char * escaped_buffer; bool ret; char str[37]; uuid_unparse(uuid, str); log_info("saving app: name='%s', %srunning, %s, level '%s', commandline='%s'", name, running ? "" : "not ", terminal ? "terminal" : "shell", level, command); ret = false; escaped_buffer = malloc(max_escaped_length(ladish_max(strlen(name), strlen(command))) + 1); if (escaped_buffer == NULL) { log_error("malloc() failed."); goto exit; } if (!ladish_write_indented_string(fd, indent, "")) { goto free_buffer; } unescaped_string = command; escaped_string = escaped_buffer; escape(&unescaped_string, &escaped_string, LADISH_ESCAPE_FLAG_ALL); *escaped_string = 0; if (!ladish_write_string(fd, escaped_buffer)) { goto free_buffer; } if (!ladish_write_string(fd, "\n")) { goto free_buffer; } ret = true; free_buffer: free(escaped_buffer); exit: return ret; } #undef ctx_ptr #undef indent #undef fd bool ladish_write_vgraph(int fd, int indent, ladish_graph_handle vgraph, ladish_app_supervisor_handle app_supervisor) { struct ladish_write_vgraph_context context; ladish_check_integrity(); context.fd = fd; context.indent = indent + 1; context.app_supervisor = app_supervisor; if (!ladish_write_indented_string(fd, indent, "\n")) { return false; } if (!ladish_graph_iterate_nodes( vgraph, &context, ladish_save_vgraph_client_begin, ladish_save_vgraph_port, ladish_save_vgraph_client_end)) { log_error("ladish_graph_iterate_nodes() failed"); return false; } if (!ladish_write_indented_string(fd, indent, "\n")) { return false; } if (!ladish_write_indented_string(fd, indent, "\n")) { return false; } if (!ladish_graph_iterate_connections(vgraph, &context, ladish_save_vgraph_connection)) { log_error("ladish_graph_iterate_connections() failed"); return false; } if (!ladish_write_indented_string(fd, indent, "\n")) { return false; } if (!ladish_write_indented_string(fd, indent, "\n")) { return false; } if (!ladish_app_supervisor_enum(app_supervisor, &context, ladish_save_app)) { return false; } if (!ladish_write_indented_string(fd, indent, "\n")) { return false; } return true; } /********************/ /* write jack graph */ /********************/ struct ladish_write_jack_context { int fd; int indent; ladish_graph_handle vgraph_filter; ladish_app_supervisor_handle app_supervisor; bool client_vgraph_match; bool a2j; bool client_visible; }; static bool ladish_save_jack_client_write_prolog(int fd, int indent, ladish_client_handle client_handle, const char * client_name) { uuid_t uuid; char str[37]; ladish_client_get_uuid(client_handle, uuid); uuid_unparse(uuid, str); log_info("saving jack client '%s' (%s)", client_name, str); if (!ladish_write_indented_string(fd, indent, "\n")) { return false; } if (!ladish_write_indented_string(fd, indent + 1, "\n")) { return false; } return true; } #define fd (((struct ladish_write_jack_context *)context)->fd) #define indent (((struct ladish_write_jack_context *)context)->indent) #define ctx_ptr ((struct ladish_write_jack_context *)context) static bool ladish_save_jack_client_begin( void * context, ladish_graph_handle UNUSED(graph_handle), bool hidden, ladish_client_handle client_handle, const char * client_name, void ** UNUSED(client_iteration_context_ptr_ptr)) { void * vgraph; vgraph = ladish_client_get_vgraph(client_handle); ctx_ptr->client_vgraph_match = vgraph == ctx_ptr->vgraph_filter; ctx_ptr->a2j = ladish_virtualizer_is_a2j_client(client_handle); /* for the a2j client vgraph is always the studio graph. However if studio has no a2j ports, lets not write a2j client. If there is a a2j port that matched the vgraph, the prolog will get written anyway */ ctx_ptr->client_visible = (!hidden || ladish_client_has_app(client_handle)) && ctx_ptr->client_vgraph_match && !ctx_ptr->a2j; if (!ctx_ptr->client_visible) { return true; } return ladish_save_jack_client_write_prolog(fd, indent, client_handle, client_name); } static bool ladish_save_jack_client_end( void * context, ladish_graph_handle UNUSED(graph_handle), bool UNUSED(hidden), ladish_client_handle UNUSED(client_handle), const char * UNUSED(client_name), void * UNUSED(client_iteration_context_ptr)) { if (!ctx_ptr->client_visible) { return true; } if (!ladish_write_indented_string(fd, indent + 1, "\n")) { return false; } if (!ladish_write_indented_string(fd, indent, "\n")) { return false; } return true; } static bool ladish_save_jack_port( void * context, ladish_graph_handle UNUSED(graph_handle), bool hidden, void * UNUSED(client_iteration_context_ptr), ladish_client_handle client_handle, const char * client_name, ladish_port_handle port_handle, const char * port_name, uint32_t UNUSED(port_type), uint32_t UNUSED(port_flags)) { uuid_t uuid; char str[37]; if (hidden && !ladish_port_has_app(port_handle)) { return true; } /* check vgraph for a2j ports */ if (ctx_ptr->a2j && ctx_ptr->vgraph_filter == ladish_port_get_vgraph(port_handle)) { if (!ctx_ptr->client_visible) { if (!ladish_save_jack_client_write_prolog(fd, indent, client_handle, client_name)) { return false; } ctx_ptr->client_visible = true; } } if (!ctx_ptr->client_visible) { return true; } /* skip hidden ports of running apps */ if (hidden && !is_hidden_port_interesting(ctx_ptr->app_supervisor, client_handle, port_handle)) { log_info("skippping hidden vgraph port '%s':'%s'", client_name, port_name); return true; } ladish_port_get_uuid(port_handle, uuid); uuid_unparse(uuid, str); log_info("saving jack port '%s':'%s' (%s)", client_name, port_name, str); if (!ladish_write_indented_string(fd, indent + 2, "\n")) { return false; } return true; } #undef ctx_ptr #undef indent #undef fd bool ladish_write_jgraph(int fd, int indent, ladish_graph_handle vgraph, ladish_app_supervisor_handle app_supervisor) { struct ladish_write_jack_context context; ladish_check_integrity(); if (!ladish_write_indented_string(fd, indent, "\n")) { return false; } context.fd = fd; context.indent = indent + 1; context.vgraph_filter = vgraph; context.app_supervisor = app_supervisor; if (!ladish_graph_iterate_nodes( ladish_studio_get_jack_graph(), &context, ladish_save_jack_client_begin, ladish_save_jack_port, ladish_save_jack_client_end)) { log_error("ladish_graph_iterate_nodes() failed"); return false; } if (!ladish_write_indented_string(fd, indent, "\n")) { return false; } return true; }