MIDI port pretty names (#498)
[jack2.git] / common / JackMetadata.cpp
blobdadb7bdf87a6bd655069e2e94426766e8c0a3e9e
1 /*
2 Copyright (C) 2011 David Robillard
3 Copyright (C) 2013 Paul Davis
5 This program is free software; you can redistribute it and/or modify it
6 under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or (at
8 your option) any later version.
10 This program is distributed in the hope that it will be useful, but WITHOUT
11 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
13 License for more details.
15 You should have received a copy of the GNU Lesser General Public License
16 along with this program; if not, write to the Free Software Foundation,
17 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 #include "JackMetadata.h"
22 #include "JackClient.h"
24 #include <string.h>
25 #include <limits.h>
28 LIB_EXPORT const char* JACK_METADATA_PRETTY_NAME = "http://jackaudio.org/metadata/pretty-name";
29 LIB_EXPORT const char* JACK_METADATA_HARDWARE = "http://jackaudio.org/metadata/hardware";
30 LIB_EXPORT const char* JACK_METADATA_CONNECTED = "http://jackaudio.org/metadata/connected";
31 LIB_EXPORT const char* JACK_METADATA_PORT_GROUP = "http://jackaudio.org/metadata/port-group";
32 LIB_EXPORT const char* JACK_METADATA_ICON_SMALL = "http://jackaudio.org/metadata/icon-small";
33 LIB_EXPORT const char* JACK_METADATA_ICON_LARGE = "http://jackaudio.org/metadata/icon-large";
36 namespace Jack
39 JackMetadata::JackMetadata(const char* server_name)
40 #if HAVE_DB
41 : fDB(NULL), fDBenv(NULL)
42 #endif
44 PropertyInit(server_name);
47 JackMetadata::~JackMetadata()
49 #if HAVE_DB
50 if (fDB) {
51 fDB->close (fDB, 0);
52 fDB = NULL;
54 if (fDBenv) {
55 fDBenv->close (fDBenv, 0);
56 fDBenv = 0;
58 #endif
61 int JackMetadata::PropertyInit(const char* server_name)
63 #if HAVE_DB
65 int ret;
66 char dbpath[PATH_MAX + 1];
68 /* idempotent */
70 if (fDBenv) {
71 return 0;
74 if ((ret = db_env_create (&fDBenv, 0)) != 0) {
75 jack_error ("cannot initialize DB environment: %s\n", db_strerror (ret));
76 return -1;
79 if ((ret = fDBenv->open (fDBenv, jack_server_dir /*FIXME:(server_name, server_dir)*/, DB_CREATE | DB_INIT_LOCK | DB_INIT_MPOOL | DB_THREAD, 0)) != 0) {
80 jack_error ("cannot open DB environment: %s", db_strerror (ret));
81 return -1;
84 if ((ret = db_create (&fDB, fDBenv, 0)) != 0) {
85 jack_error ("Cannot initialize metadata DB (%s)", db_strerror (ret));
86 return -1;
89 snprintf (dbpath, sizeof(dbpath), "%s/%s", jack_server_dir /*FIXME:(server_name, server_dir)*/, "metadata.db");
91 if ((ret = fDB->open (fDB, NULL, dbpath, NULL, DB_HASH, DB_CREATE | DB_THREAD, 0666)) != 0) {
92 jack_error ("Cannot open metadata DB at %s: %s", dbpath, db_strerror (ret));
93 fDB->close (fDB, 0);
94 fDB = NULL;
95 return -1;
98 return 0;
100 #else // !HAVE_DB
101 return -1;
102 #endif
105 int JackMetadata::PropertyChangeNotify(JackClient* client, jack_uuid_t subject, const char* key, jack_property_change_t change)
107 /* the engine passes in a NULL client when it removes metadata during port or client removal
110 if (client == NULL) {
111 return 0;
114 return client->PropertyChangeNotify(subject, key, change);
117 #if HAVE_DB
118 void JackMetadata::MakeKeyDbt(DBT* dbt, jack_uuid_t subject, const char* key)
120 char ustr[JACK_UUID_STRING_SIZE];
121 size_t len1, len2;
123 memset (dbt, 0, sizeof(DBT));
124 memset (ustr, 0, JACK_UUID_STRING_SIZE);
125 jack_uuid_unparse (subject, ustr);
126 len1 = JACK_UUID_STRING_SIZE;
127 len2 = strlen (key) + 1;
128 dbt->size = len1 + len2;
129 dbt->data = malloc (dbt->size);
130 memcpy (dbt->data, ustr, len1); // copy subject+null
131 memcpy ((char *)dbt->data + len1, key, len2); // copy key+null
133 #endif
135 int JackMetadata::SetProperty(JackClient* client, jack_uuid_t subject, const char* key, const char* value, const char* type)
137 #if HAVE_DB
139 DBT d_key;
140 DBT data;
141 int ret;
142 size_t len1, len2;
143 jack_property_change_t change;
145 if (!key || key[0] == '\0') {
146 jack_error ("empty key string for metadata not allowed");
147 return -1;
150 if (!value || value[0] == '\0') {
151 jack_error ("empty value string for metadata not allowed");
152 return -1;
155 if (PropertyInit(NULL)) {
156 return -1;
159 /* build a key */
161 MakeKeyDbt(&d_key, subject, key);
163 /* build data */
165 memset (&data, 0, sizeof(data));
167 len1 = strlen (value) + 1;
168 if (type && type[0] != '\0') {
169 len2 = strlen (type) + 1;
170 } else {
171 len2 = 0;
174 data.size = len1 + len2;
175 data.data = malloc (data.size);
176 memcpy (data.data, value, len1);
178 if (len2) {
179 memcpy ((char *)data.data + len1, type, len2);
182 if (fDB->exists (fDB, NULL, &d_key, 0) == DB_NOTFOUND) {
183 change = PropertyCreated;
184 } else {
185 change = PropertyChanged;
188 if ((ret = fDB->put (fDB, NULL, &d_key, &data, 0)) != 0) {
189 char ustr[JACK_UUID_STRING_SIZE];
190 jack_uuid_unparse (subject, ustr);
191 jack_error ("Cannot store metadata for %s/%s (%s)", ustr, key, db_strerror (ret));
192 if (d_key.size > 0) {
193 free (d_key.data);
195 if (data.size > 0) {
196 free (data.data);
198 return -1;
201 PropertyChangeNotify(client, subject, key, change);
203 if (d_key.size > 0) {
204 free (d_key.data);
206 if (data.size > 0) {
207 free (data.data);
210 return 0;
212 #else // !HAVE_DB
213 return -1;
214 #endif
217 int JackMetadata::GetProperty(jack_uuid_t subject, const char* key, char** value, char** type)
219 #if HAVE_DB
221 DBT d_key;
222 DBT data;
223 int ret;
224 size_t len1, len2;
226 if (key == NULL || key[0] == '\0') {
227 return -1;
230 if (PropertyInit(NULL)) {
231 return -1;
234 /* build a key */
236 MakeKeyDbt(&d_key, subject, key);
238 /* setup data DBT */
240 memset (&data, 0, sizeof(data));
241 data.flags = DB_DBT_MALLOC;
243 if ((ret = fDB->get (fDB, NULL, &d_key, &data, 0)) != 0) {
244 if (ret != DB_NOTFOUND) {
245 char ustr[JACK_UUID_STRING_SIZE];
246 jack_uuid_unparse (subject, ustr);
247 jack_error ("Cannot retrieve metadata for %s/%s (%s)", ustr, key, db_strerror (ret));
249 if (d_key.size > 0) {
250 free (d_key.data);
252 if (data.size > 0) {
253 free (data.data);
255 return -1;
258 /* result must have at least 1 char plus 1 null to be valid
259 (old rule was:
260 result must have at least 2 chars plus 2 nulls to be valid
264 if (data.size < 2) {
265 if (d_key.size > 0) {
266 free (d_key.data);
268 if (data.size > 0) {
269 free (data.data);
271 return -1;
274 len1 = strlen ((const char*)data.data) + 1;
275 (*value) = (char*)malloc (len1);
276 memcpy (*value, data.data, len1);
278 if (len1 < data.size) {
279 len2 = strlen ((const char*)data.data + len1) + 1;
281 (*type) = (char*)malloc (len2);
282 memcpy (*type, (const char *)data.data + len1, len2);
283 } else {
284 /* no type specified, assume default */
285 *type = NULL;
288 if (d_key.size > 0) {
289 free (d_key.data);
291 if (data.size > 0) {
292 free (data.data);
295 return 0;
297 #else // !HAVE_DB
298 return -1;
299 #endif
302 int JackMetadata::GetProperties(jack_uuid_t subject, jack_description_t* desc)
304 #if HAVE_DB
306 DBT key;
307 DBT data;
308 DBC* cursor;
309 int ret;
310 size_t len1, len2;
311 size_t cnt = 0;
312 char ustr[JACK_UUID_STRING_SIZE];
313 size_t props_size = 0;
314 jack_property_t* prop;
316 desc->properties = NULL;
317 desc->property_cnt = 0;
319 memset (ustr, 0, JACK_UUID_STRING_SIZE);
320 jack_uuid_unparse (subject, ustr);
322 if (PropertyInit(NULL)) {
323 return -1;
327 if ((ret = fDB->cursor (fDB, NULL, &cursor, 0)) != 0) {
328 jack_error ("Cannot create cursor for metadata search (%s)", db_strerror (ret));
329 return -1;
332 memset (&key, 0, sizeof(key));
333 memset (&data, 0, sizeof(data));
334 data.flags = DB_DBT_MALLOC;
336 while ((ret = cursor->get (cursor, &key, &data, DB_NEXT)) == 0) {
338 /* require 2 extra chars (data+null) for key,
339 which is composed of UUID str plus a key name
342 if (key.size < JACK_UUID_STRING_SIZE + 2) {
343 /* if (key.size > 0) free(key.data); */
344 if (data.size > 0) {
345 free (data.data);
347 continue;
350 if (memcmp (ustr, key.data, JACK_UUID_STRING_SIZE) != 0) {
351 /* not relevant */
352 /* if (key.size > 0) free(key.data); */
353 if (data.size > 0) {
354 free (data.data);
356 continue;
359 /* result must have at least 1 char plus 1 null to be valid
360 (old rule was:
361 result must have at least 2 chars plus 2 nulls to be valid
365 if (data.size < 2) {
366 /* if (key.size > 0) free(key.data); */
367 if (data.size > 0) {
368 free (data.data);
370 continue;
373 /* realloc array if necessary */
375 if (cnt == props_size) {
376 if (props_size == 0) {
377 props_size = 8; /* a rough guess at a likely upper bound for the number of properties */
378 } else {
379 props_size *= 2;
382 desc->properties = (jack_property_t*)realloc (desc->properties, sizeof(jack_property_t) * props_size);
385 prop = &desc->properties[cnt];
387 /* store UUID/subject */
389 jack_uuid_copy (&desc->subject, subject);
391 /* copy key (without leading UUID as subject */
393 len1 = key.size - JACK_UUID_STRING_SIZE;
394 prop->key = (char*)malloc (len1);
395 memcpy ((char*)prop->key, (const char *)key.data + JACK_UUID_STRING_SIZE, len1);
397 /* copy data (which contains 1 or 2 null terminated strings, the value
398 and optionally a MIME type.
401 len1 = strlen ((const char*)data.data) + 1;
402 prop->data = (char*)malloc (len1);
403 memcpy ((char*)prop->data, data.data, len1);
405 if (len1 < data.size) {
406 len2 = strlen ((const char *)data.data + len1) + 1;
408 prop->type = (char*)malloc (len2);
409 memcpy ((char*)prop->type, (const char *)data.data + len1, len2);
410 } else {
411 /* no type specified, assume default */
412 prop->type = NULL;
415 /* if (key.size > 0) free(key.data); */
416 if (data.size > 0) {
417 free (data.data);
420 ++cnt;
423 cursor->close (cursor);
424 desc->property_cnt = cnt;
426 return cnt;
428 #else // !HAVE_DB
429 return -1;
430 #endif
433 int JackMetadata::GetAllProperties(jack_description_t** descriptions)
435 #if HAVE_DB
437 DBT key;
438 DBT data;
439 DBC* cursor;
440 int ret;
441 size_t dcnt = 0;
442 size_t dsize = 0;
443 size_t n = 0;
444 jack_description_t* desc = NULL;
445 jack_uuid_t uuid = JACK_UUID_EMPTY_INITIALIZER;
446 jack_description_t* current_desc = NULL;
447 jack_property_t* current_prop = NULL;
448 size_t len1, len2;
450 if (PropertyInit(NULL)) {
451 return -1;
454 if ((ret = fDB->cursor (fDB, NULL, &cursor, 0)) != 0) {
455 jack_error ("Cannot create cursor for metadata search (%s)", db_strerror (ret));
456 return -1;
459 memset (&key, 0, sizeof(key));
460 memset (&data, 0, sizeof(data));
461 data.flags = DB_DBT_MALLOC;
463 dsize = 8; /* initial guess at number of descriptions we need */
464 dcnt = 0;
465 desc = (jack_description_t*)malloc (dsize * sizeof(jack_description_t));
467 while ((ret = cursor->get (cursor, &key, &data, DB_NEXT)) == 0) {
469 /* require 2 extra chars (data+null) for key,
470 which is composed of UUID str plus a key name
473 if (key.size < JACK_UUID_STRING_SIZE + 2) {
474 /* if (key.size > 0) free(key.data); */
475 if (data.size > 0) {
476 free (data.data);
478 continue;
481 if (jack_uuid_parse ((const char *)key.data, &uuid) != 0) {
482 continue;
485 /* do we have an existing description for this UUID */
487 for (n = 0; n < dcnt; ++n) {
488 if (jack_uuid_compare (uuid, desc[n].subject) == 0) {
489 break;
493 if (n == dcnt) {
494 /* we do not have an existing description, so grow the array */
496 if (dcnt == dsize) {
497 dsize *= 2;
498 desc = (jack_description_t*)realloc (desc, sizeof(jack_description_t) * dsize);
501 /* initialize */
503 desc[n].property_size = 0;
504 desc[n].property_cnt = 0;
505 desc[n].properties = NULL;
507 /* set up UUID */
509 jack_uuid_copy (&desc[n].subject, uuid);
510 dcnt++;
513 current_desc = &desc[n];
515 /* see if there is room for the new property or if we need to realloc
518 if (current_desc->property_cnt == current_desc->property_size) {
519 if (current_desc->property_size == 0) {
520 current_desc->property_size = 8;
521 } else {
522 current_desc->property_size *= 2;
525 current_desc->properties = (jack_property_t*)realloc (current_desc->properties, sizeof(jack_property_t) * current_desc->property_size);
528 current_prop = &current_desc->properties[current_desc->property_cnt++];
530 /* copy key (without leading UUID) */
532 len1 = key.size - JACK_UUID_STRING_SIZE;
533 current_prop->key = (char*)malloc (len1);
534 memcpy ((char*)current_prop->key, (const char *)key.data + JACK_UUID_STRING_SIZE, len1);
536 /* copy data (which contains 1 or 2 null terminated strings, the value
537 and optionally a MIME type.
540 len1 = strlen ((const char *)data.data) + 1;
541 current_prop->data = (char*)malloc (len1);
542 memcpy ((char*)current_prop->data, data.data, len1);
544 if (len1 < data.size) {
545 len2 = strlen ((const char *)data.data + len1) + 1;
547 current_prop->type = (char*)malloc (len2);
548 memcpy ((char*)current_prop->type, (const char *)data.data + len1, len2);
549 } else {
550 /* no type specified, assume default */
551 current_prop->type = NULL;
554 /* if (key.size > 0) free(key.data); */
555 if (data.size > 0) {
556 free (data.data);
560 cursor->close (cursor);
562 (*descriptions) = desc;
564 return dcnt;
566 #else // !HAVE_DB
567 return -1;
568 #endif
571 int JackMetadata::GetDescription(jack_uuid_t subject, jack_description_t* desc)
573 return 0;
576 int JackMetadata::GetAllDescriptions(jack_description_t** descs)
578 return 0;
581 void JackMetadata::FreeDescription(jack_description_t* desc, int free_actual_description_too)
583 uint32_t n;
585 for (n = 0; n < desc->property_cnt; ++n) {
586 free ((char*)desc->properties[n].key);
587 free ((char*)desc->properties[n].data);
588 if (desc->properties[n].type) {
589 free ((char*)desc->properties[n].type);
593 free (desc->properties);
595 if (free_actual_description_too) {
596 free (desc);
600 int JackMetadata::RemoveProperty(JackClient* client, jack_uuid_t subject, const char* key)
602 #if HAVE_DB
604 DBT d_key;
605 int ret;
607 if (PropertyInit(NULL)) {
608 return -1;
611 MakeKeyDbt(&d_key, subject, key);
612 if ((ret = fDB->del (fDB, NULL, &d_key, 0)) != 0) {
613 jack_error ("Cannot delete key %s (%s)", key, db_strerror (ret));
614 if (d_key.size > 0) {
615 free (d_key.data);
617 return -1;
620 PropertyChangeNotify(client, subject, key, PropertyDeleted);
622 if (d_key.size > 0) {
623 free (d_key.data);
626 return 0;
628 #else // !HAVE_DB
629 return -1;
630 #endif
633 int JackMetadata::RemoveProperties(JackClient* client, jack_uuid_t subject)
635 #if HAVE_DB
637 DBT key;
638 DBT data;
639 DBC* cursor;
640 int ret;
641 char ustr[JACK_UUID_STRING_SIZE];
642 int retval = 0;
643 uint32_t cnt = 0;
645 memset (ustr, 0, JACK_UUID_STRING_SIZE);
646 jack_uuid_unparse (subject, ustr);
648 if (PropertyInit(NULL)) {
649 return -1;
652 if ((ret = fDB->cursor (fDB, NULL, &cursor, 0)) != 0) {
653 jack_error ("Cannot create cursor for metadata search (%s)", db_strerror (ret));
654 return -1;
657 memset (&key, 0, sizeof(key));
658 memset (&data, 0, sizeof(data));
659 data.flags = DB_DBT_MALLOC;
661 while ((ret = cursor->get (cursor, &key, &data, DB_NEXT)) == 0) {
663 /* require 2 extra chars (data+null) for key,
664 which is composed of UUID str plus a key name
667 if (key.size < JACK_UUID_STRING_SIZE + 2) {
668 /* if (key.size > 0) free(key.data); */
669 if (data.size > 0) {
670 free (data.data);
672 continue;
675 if (memcmp (ustr, key.data, JACK_UUID_STRING_SIZE) != 0) {
676 /* not relevant */
677 /* if (key.size > 0) free(key.data); */
678 if (data.size > 0) {
679 free (data.data);
681 continue;
684 if ((ret = cursor->del (cursor, 0)) != 0) {
685 jack_error ("cannot delete property (%s)", db_strerror (ret));
686 /* don't return -1 here since this would leave things
687 even more inconsistent. wait till the cursor is finished
689 retval = -1;
691 cnt++;
693 /* if (key.size > 0) free(key.data); */
694 if (data.size > 0) {
695 free (data.data);
699 cursor->close (cursor);
701 if (cnt) {
702 PropertyChangeNotify(client, subject, NULL, PropertyDeleted);
705 if (retval) {
706 return -1;
709 return cnt;
711 #else // !HAVE_DB
712 return -1;
713 #endif
716 int JackMetadata::RemoveAllProperties(JackClient* client)
718 #if HAVE_DB
720 int ret;
721 jack_uuid_t empty_uuid = JACK_UUID_EMPTY_INITIALIZER;
723 if (PropertyInit(NULL)) {
724 return -1;
727 if ((ret = fDB->truncate (fDB, NULL, NULL, 0)) != 0) {
728 jack_error ("Cannot clear properties (%s)", db_strerror (ret));
729 return -1;
732 PropertyChangeNotify(client, empty_uuid, NULL, PropertyDeleted);
734 return 0;
736 #else // !HAVE_DB
737 return -1;
738 #endif
741 } // end of namespace