Rename system_midi:* back to system:midi_*
[jack2.git] / common / JackMetadata.cpp
blob75d4b3b5e63c22713c186062c59aa8def2a821a1
1 /*
2 Copyright (C) 2011 David Robillard
3 Copyright (C) 2013 Paul Davis
4 Copyright (C) 2019 Filipe Coelho
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or (at
9 your option) any later version.
11 This program is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public License
17 along with this program; if not, write to the Free Software Foundation,
18 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 #include "JackMetadata.h"
23 #include "JackClient.h"
25 #include <string.h>
26 #include <sys/stat.h>
27 #include <limits.h>
30 LIB_EXPORT const char* JACK_METADATA_PRETTY_NAME = "http://jackaudio.org/metadata/pretty-name";
31 LIB_EXPORT const char* JACK_METADATA_HARDWARE = "http://jackaudio.org/metadata/hardware";
32 LIB_EXPORT const char* JACK_METADATA_CONNECTED = "http://jackaudio.org/metadata/connected";
33 LIB_EXPORT const char* JACK_METADATA_PORT_GROUP = "http://jackaudio.org/metadata/port-group";
34 LIB_EXPORT const char* JACK_METADATA_ICON_SMALL = "http://jackaudio.org/metadata/icon-small";
35 LIB_EXPORT const char* JACK_METADATA_ICON_LARGE = "http://jackaudio.org/metadata/icon-large";
37 namespace Jack
40 JackMetadata::JackMetadata(bool isEngine)
41 #if HAVE_DB
42 : fDB(NULL), fDBenv(NULL), fIsEngine(isEngine)
43 #endif
45 PropertyInit();
48 JackMetadata::~JackMetadata()
50 #if HAVE_DB
51 char dbpath[PATH_MAX + 1];
53 if (fDB) {
54 fDB->close (fDB, 0);
55 fDB = NULL;
57 if (fDBenv) {
58 fDBenv->close (fDBenv, 0);
59 fDBenv = NULL;
62 if (fIsEngine)
64 // cleanup after libdb, nasty!
65 snprintf (dbpath, sizeof(dbpath), "%s/jack_db/metadata.db", jack_server_dir);
66 remove (dbpath);
68 snprintf (dbpath, sizeof(dbpath), "%s/jack_db/__db.001", jack_server_dir);
69 remove (dbpath);
71 snprintf (dbpath, sizeof(dbpath), "%s/jack_db/__db.002", jack_server_dir);
72 remove (dbpath);
74 snprintf (dbpath, sizeof(dbpath), "%s/jack_db/__db.003", jack_server_dir);
75 remove (dbpath);
77 // remove our custom dir
78 snprintf (dbpath, sizeof(dbpath), "%s/jack_db", jack_server_dir);
79 rmdir (dbpath);
81 #endif
84 int JackMetadata::PropertyInit()
86 #if HAVE_DB
88 int ret;
89 char dbpath[PATH_MAX + 1];
91 /* idempotent */
93 if (fDBenv) {
94 return 0;
97 if ((ret = db_env_create (&fDBenv, 0)) != 0) {
98 jack_error ("cannot initialize DB environment: %s\n", db_strerror (ret));
99 return -1;
102 snprintf (dbpath, sizeof(dbpath), "%s/jack_db", jack_server_dir);
103 mkdir (dbpath, S_IRWXU | S_IRWXG);
105 if ((ret = fDBenv->open (fDBenv, dbpath, DB_CREATE | DB_INIT_LOCK | DB_INIT_MPOOL | DB_THREAD, 0)) != 0) {
106 jack_error ("cannot open DB environment: %s", db_strerror (ret));
107 return -1;
110 if ((ret = db_create (&fDB, fDBenv, 0)) != 0) {
111 jack_error ("Cannot initialize metadata DB (%s)", db_strerror (ret));
112 return -1;
115 snprintf (dbpath, sizeof(dbpath), "%s/jack_db/metadata.db", jack_server_dir);
116 if ((ret = fDB->open (fDB, NULL, dbpath, NULL, DB_HASH, DB_CREATE | DB_THREAD, 0666)) != 0) {
117 jack_error ("Cannot open metadata DB at %s: %s", dbpath, db_strerror (ret));
118 fDB->close (fDB, 0);
119 fDB = NULL;
120 return -1;
123 return 0;
125 #else // !HAVE_DB
126 return -1;
127 #endif
130 int JackMetadata::PropertyChangeNotify(JackClient* client, jack_uuid_t subject, const char* key, jack_property_change_t change)
132 /* the engine passes in a NULL client when it removes metadata during port or client removal
135 if (client == NULL) {
136 return 0;
139 return client->PropertyChangeNotify(subject, key, change);
142 #if HAVE_DB
143 void JackMetadata::MakeKeyDbt(DBT* dbt, jack_uuid_t subject, const char* key)
145 char ustr[JACK_UUID_STRING_SIZE];
146 size_t len1, len2;
148 memset (dbt, 0, sizeof(DBT));
149 memset (ustr, 0, JACK_UUID_STRING_SIZE);
150 jack_uuid_unparse (subject, ustr);
151 len1 = JACK_UUID_STRING_SIZE;
152 len2 = strlen (key) + 1;
153 dbt->size = len1 + len2;
154 dbt->data = malloc (dbt->size);
155 memcpy (dbt->data, ustr, len1); // copy subject+null
156 memcpy ((char *)dbt->data + len1, key, len2); // copy key+null
158 #endif
160 int JackMetadata::SetProperty(JackClient* client, jack_uuid_t subject, const char* key, const char* value, const char* type)
162 #if HAVE_DB
164 DBT d_key;
165 DBT data;
166 int ret;
167 size_t len1, len2;
168 jack_property_change_t change;
170 if (!key || key[0] == '\0') {
171 jack_error ("empty key string for metadata not allowed");
172 return -1;
175 if (!value || value[0] == '\0') {
176 jack_error ("empty value string for metadata not allowed");
177 return -1;
180 if (PropertyInit()) {
181 return -1;
184 /* build a key */
186 MakeKeyDbt(&d_key, subject, key);
188 /* build data */
190 memset (&data, 0, sizeof(data));
192 len1 = strlen (value) + 1;
193 if (type && type[0] != '\0') {
194 len2 = strlen (type) + 1;
195 } else {
196 len2 = 0;
199 data.size = len1 + len2;
200 data.data = malloc (data.size);
201 memcpy (data.data, value, len1);
203 if (len2) {
204 memcpy ((char *)data.data + len1, type, len2);
207 if (fDB->exists (fDB, NULL, &d_key, 0) == DB_NOTFOUND) {
208 change = PropertyCreated;
209 } else {
210 change = PropertyChanged;
213 if ((ret = fDB->put (fDB, NULL, &d_key, &data, 0)) != 0) {
214 char ustr[JACK_UUID_STRING_SIZE];
215 jack_uuid_unparse (subject, ustr);
216 jack_error ("Cannot store metadata for %s/%s (%s)", ustr, key, db_strerror (ret));
217 if (d_key.size > 0) {
218 free (d_key.data);
220 if (data.size > 0) {
221 free (data.data);
223 return -1;
226 PropertyChangeNotify(client, subject, key, change);
228 if (d_key.size > 0) {
229 free (d_key.data);
231 if (data.size > 0) {
232 free (data.data);
235 return 0;
237 #else // !HAVE_DB
238 return -1;
239 #endif
242 int JackMetadata::GetProperty(jack_uuid_t subject, const char* key, char** value, char** type)
244 #if HAVE_DB
246 DBT d_key;
247 DBT data;
248 int ret;
249 size_t len1, len2;
251 if (key == NULL || key[0] == '\0') {
252 return -1;
255 if (PropertyInit()) {
256 return -1;
259 /* build a key */
261 MakeKeyDbt(&d_key, subject, key);
263 /* setup data DBT */
265 memset (&data, 0, sizeof(data));
266 data.flags = DB_DBT_MALLOC;
268 if ((ret = fDB->get (fDB, NULL, &d_key, &data, 0)) != 0) {
269 if (ret != DB_NOTFOUND) {
270 char ustr[JACK_UUID_STRING_SIZE];
271 jack_uuid_unparse (subject, ustr);
272 jack_error ("Cannot retrieve metadata for %s/%s (%s)", ustr, key, db_strerror (ret));
274 if (d_key.size > 0) {
275 free (d_key.data);
277 if (data.size > 0) {
278 free (data.data);
280 return -1;
283 /* result must have at least 1 char plus 1 null to be valid
284 (old rule was:
285 result must have at least 2 chars plus 2 nulls to be valid
289 if (data.size < 2) {
290 if (d_key.size > 0) {
291 free (d_key.data);
293 if (data.size > 0) {
294 free (data.data);
296 return -1;
299 len1 = strlen ((const char*)data.data) + 1;
300 (*value) = (char*)malloc (len1);
301 memcpy (*value, data.data, len1);
303 if (len1 < data.size) {
304 len2 = strlen ((const char*)data.data + len1) + 1;
306 (*type) = (char*)malloc (len2);
307 memcpy (*type, (const char *)data.data + len1, len2);
308 } else {
309 /* no type specified, assume default */
310 *type = NULL;
313 if (d_key.size > 0) {
314 free (d_key.data);
316 if (data.size > 0) {
317 free (data.data);
320 return 0;
322 #else // !HAVE_DB
323 return -1;
324 #endif
327 int JackMetadata::GetProperties(jack_uuid_t subject, jack_description_t* desc)
329 #if HAVE_DB
331 DBT key;
332 DBT data;
333 DBC* cursor;
334 int ret;
335 size_t len1, len2;
336 size_t cnt = 0;
337 char ustr[JACK_UUID_STRING_SIZE];
338 size_t props_size = 0;
339 jack_property_t* prop;
341 desc->properties = NULL;
342 desc->property_cnt = 0;
344 memset (ustr, 0, JACK_UUID_STRING_SIZE);
345 jack_uuid_unparse (subject, ustr);
347 if (PropertyInit()) {
348 return -1;
352 if ((ret = fDB->cursor (fDB, NULL, &cursor, 0)) != 0) {
353 jack_error ("Cannot create cursor for metadata search (%s)", db_strerror (ret));
354 return -1;
357 memset (&key, 0, sizeof(key));
358 memset (&data, 0, sizeof(data));
359 data.flags = DB_DBT_MALLOC;
361 while ((ret = cursor->get (cursor, &key, &data, DB_NEXT)) == 0) {
363 /* require 2 extra chars (data+null) for key,
364 which is composed of UUID str plus a key name
367 if (key.size < JACK_UUID_STRING_SIZE + 2) {
368 /* if (key.size > 0) free(key.data); */
369 if (data.size > 0) {
370 free (data.data);
372 continue;
375 if (memcmp (ustr, key.data, JACK_UUID_STRING_SIZE) != 0) {
376 /* not relevant */
377 /* if (key.size > 0) free(key.data); */
378 if (data.size > 0) {
379 free (data.data);
381 continue;
384 /* result must have at least 1 char plus 1 null to be valid
385 (old rule was:
386 result must have at least 2 chars plus 2 nulls to be valid
390 if (data.size < 2) {
391 /* if (key.size > 0) free(key.data); */
392 if (data.size > 0) {
393 free (data.data);
395 continue;
398 /* realloc array if necessary */
400 if (cnt == props_size) {
401 if (props_size == 0) {
402 props_size = 8; /* a rough guess at a likely upper bound for the number of properties */
403 } else {
404 props_size *= 2;
407 desc->properties = (jack_property_t*)realloc (desc->properties, sizeof(jack_property_t) * props_size);
410 prop = &desc->properties[cnt];
412 /* store UUID/subject */
414 jack_uuid_copy (&desc->subject, subject);
416 /* copy key (without leading UUID as subject */
418 len1 = key.size - JACK_UUID_STRING_SIZE;
419 prop->key = (char*)malloc (len1);
420 memcpy ((char*)prop->key, (const char *)key.data + JACK_UUID_STRING_SIZE, len1);
422 /* copy data (which contains 1 or 2 null terminated strings, the value
423 and optionally a MIME type.
426 len1 = strlen ((const char*)data.data) + 1;
427 prop->data = (char*)malloc (len1);
428 memcpy ((char*)prop->data, data.data, len1);
430 if (len1 < data.size) {
431 len2 = strlen ((const char *)data.data + len1) + 1;
433 prop->type = (char*)malloc (len2);
434 memcpy ((char*)prop->type, (const char *)data.data + len1, len2);
435 } else {
436 /* no type specified, assume default */
437 prop->type = NULL;
440 /* if (key.size > 0) free(key.data); */
441 if (data.size > 0) {
442 free (data.data);
445 ++cnt;
448 cursor->close (cursor);
449 desc->property_cnt = cnt;
451 return cnt;
453 #else // !HAVE_DB
454 return -1;
455 #endif
458 int JackMetadata::GetAllProperties(jack_description_t** descriptions)
460 #if HAVE_DB
462 DBT key;
463 DBT data;
464 DBC* cursor;
465 int ret;
466 size_t dcnt = 0;
467 size_t dsize = 0;
468 size_t n = 0;
469 jack_description_t* desc = NULL;
470 jack_uuid_t uuid = JACK_UUID_EMPTY_INITIALIZER;
471 jack_description_t* current_desc = NULL;
472 jack_property_t* current_prop = NULL;
473 size_t len1, len2;
475 if (PropertyInit()) {
476 return -1;
479 if ((ret = fDB->cursor (fDB, NULL, &cursor, 0)) != 0) {
480 jack_error ("Cannot create cursor for metadata search (%s)", db_strerror (ret));
481 return -1;
484 memset (&key, 0, sizeof(key));
485 memset (&data, 0, sizeof(data));
486 data.flags = DB_DBT_MALLOC;
488 dsize = 8; /* initial guess at number of descriptions we need */
489 dcnt = 0;
490 desc = (jack_description_t*)malloc (dsize * sizeof(jack_description_t));
492 while ((ret = cursor->get (cursor, &key, &data, DB_NEXT)) == 0) {
494 /* require 2 extra chars (data+null) for key,
495 which is composed of UUID str plus a key name
498 if (key.size < JACK_UUID_STRING_SIZE + 2) {
499 /* if (key.size > 0) free(key.data); */
500 if (data.size > 0) {
501 free (data.data);
503 continue;
506 if (jack_uuid_parse ((const char *)key.data, &uuid) != 0) {
507 continue;
510 /* do we have an existing description for this UUID */
512 for (n = 0; n < dcnt; ++n) {
513 if (jack_uuid_compare (uuid, desc[n].subject) == 0) {
514 break;
518 if (n == dcnt) {
519 /* we do not have an existing description, so grow the array */
521 if (dcnt == dsize) {
522 dsize *= 2;
523 desc = (jack_description_t*)realloc (desc, sizeof(jack_description_t) * dsize);
526 /* initialize */
528 desc[n].property_size = 0;
529 desc[n].property_cnt = 0;
530 desc[n].properties = NULL;
532 /* set up UUID */
534 jack_uuid_copy (&desc[n].subject, uuid);
535 dcnt++;
538 current_desc = &desc[n];
540 /* see if there is room for the new property or if we need to realloc
543 if (current_desc->property_cnt == current_desc->property_size) {
544 if (current_desc->property_size == 0) {
545 current_desc->property_size = 8;
546 } else {
547 current_desc->property_size *= 2;
550 current_desc->properties = (jack_property_t*)realloc (current_desc->properties, sizeof(jack_property_t) * current_desc->property_size);
553 current_prop = &current_desc->properties[current_desc->property_cnt++];
555 /* copy key (without leading UUID) */
557 len1 = key.size - JACK_UUID_STRING_SIZE;
558 current_prop->key = (char*)malloc (len1);
559 memcpy ((char*)current_prop->key, (const char *)key.data + JACK_UUID_STRING_SIZE, len1);
561 /* copy data (which contains 1 or 2 null terminated strings, the value
562 and optionally a MIME type.
565 len1 = strlen ((const char *)data.data) + 1;
566 current_prop->data = (char*)malloc (len1);
567 memcpy ((char*)current_prop->data, data.data, len1);
569 if (len1 < data.size) {
570 len2 = strlen ((const char *)data.data + len1) + 1;
572 current_prop->type = (char*)malloc (len2);
573 memcpy ((char*)current_prop->type, (const char *)data.data + len1, len2);
574 } else {
575 /* no type specified, assume default */
576 current_prop->type = NULL;
579 /* if (key.size > 0) free(key.data); */
580 if (data.size > 0) {
581 free (data.data);
585 cursor->close (cursor);
587 (*descriptions) = desc;
589 return dcnt;
591 #else // !HAVE_DB
592 return -1;
593 #endif
596 int JackMetadata::GetDescription(jack_uuid_t subject, jack_description_t* desc)
598 return 0;
601 int JackMetadata::GetAllDescriptions(jack_description_t** descs)
603 return 0;
606 void JackMetadata::FreeDescription(jack_description_t* desc, int free_actual_description_too)
608 uint32_t n;
610 for (n = 0; n < desc->property_cnt; ++n) {
611 free ((char*)desc->properties[n].key);
612 free ((char*)desc->properties[n].data);
613 if (desc->properties[n].type) {
614 free ((char*)desc->properties[n].type);
618 free (desc->properties);
620 if (free_actual_description_too) {
621 free (desc);
625 int JackMetadata::RemoveProperty(JackClient* client, jack_uuid_t subject, const char* key)
627 #if HAVE_DB
629 DBT d_key;
630 int ret;
632 if (PropertyInit()) {
633 return -1;
636 MakeKeyDbt(&d_key, subject, key);
637 if ((ret = fDB->del (fDB, NULL, &d_key, 0)) != 0) {
638 jack_error ("Cannot delete key %s (%s)", key, db_strerror (ret));
639 if (d_key.size > 0) {
640 free (d_key.data);
642 return -1;
645 PropertyChangeNotify(client, subject, key, PropertyDeleted);
647 if (d_key.size > 0) {
648 free (d_key.data);
651 return 0;
653 #else // !HAVE_DB
654 return -1;
655 #endif
658 int JackMetadata::RemoveProperties(JackClient* client, jack_uuid_t subject)
660 #if HAVE_DB
662 DBT key;
663 DBT data;
664 DBC* cursor;
665 int ret;
666 char ustr[JACK_UUID_STRING_SIZE];
667 int retval = 0;
668 uint32_t cnt = 0;
670 memset (ustr, 0, JACK_UUID_STRING_SIZE);
671 jack_uuid_unparse (subject, ustr);
673 if (PropertyInit() || fDB == NULL) {
674 return -1;
677 if ((ret = fDB->cursor (fDB, NULL, &cursor, 0)) != 0) {
678 jack_error ("Cannot create cursor for metadata search (%s)", db_strerror (ret));
679 return -1;
682 memset (&key, 0, sizeof(key));
683 memset (&data, 0, sizeof(data));
684 data.flags = DB_DBT_MALLOC;
686 while ((ret = cursor->get (cursor, &key, &data, DB_NEXT)) == 0) {
688 /* require 2 extra chars (data+null) for key,
689 which is composed of UUID str plus a key name
692 if (key.size < JACK_UUID_STRING_SIZE + 2) {
693 /* if (key.size > 0) free(key.data); */
694 if (data.size > 0) {
695 free (data.data);
697 continue;
700 if (memcmp (ustr, key.data, JACK_UUID_STRING_SIZE) != 0) {
701 /* not relevant */
702 /* if (key.size > 0) free(key.data); */
703 if (data.size > 0) {
704 free (data.data);
706 continue;
709 if ((ret = cursor->del (cursor, 0)) != 0) {
710 jack_error ("cannot delete property (%s)", db_strerror (ret));
711 /* don't return -1 here since this would leave things
712 even more inconsistent. wait till the cursor is finished
714 retval = -1;
716 cnt++;
718 /* if (key.size > 0) free(key.data); */
719 if (data.size > 0) {
720 free (data.data);
724 cursor->close (cursor);
726 if (cnt) {
727 PropertyChangeNotify(client, subject, NULL, PropertyDeleted);
730 if (retval) {
731 return -1;
734 return cnt;
736 #else // !HAVE_DB
737 return -1;
738 #endif
741 int JackMetadata::RemoveAllProperties(JackClient* client)
743 #if HAVE_DB
745 int ret;
746 jack_uuid_t empty_uuid = JACK_UUID_EMPTY_INITIALIZER;
748 if (PropertyInit()) {
749 return -1;
752 if ((ret = fDB->truncate (fDB, NULL, NULL, 0)) != 0) {
753 jack_error ("Cannot clear properties (%s)", db_strerror (ret));
754 return -1;
757 PropertyChangeNotify(client, empty_uuid, NULL, PropertyDeleted);
759 return 0;
761 #else // !HAVE_DB
762 return -1;
763 #endif
766 } // end of namespace