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"
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";
40 JackMetadata::JackMetadata(bool isEngine
)
42 : fDB(NULL
), fDBenv(NULL
), fIsEngine(isEngine
)
48 JackMetadata::~JackMetadata()
51 char dbpath
[PATH_MAX
+ 1];
58 fDBenv
->close (fDBenv
, 0);
64 // cleanup after libdb, nasty!
65 snprintf (dbpath
, sizeof(dbpath
), "%s/jack_db/metadata.db", jack_server_dir
);
68 snprintf (dbpath
, sizeof(dbpath
), "%s/jack_db/__db.001", jack_server_dir
);
71 snprintf (dbpath
, sizeof(dbpath
), "%s/jack_db/__db.002", jack_server_dir
);
74 snprintf (dbpath
, sizeof(dbpath
), "%s/jack_db/__db.003", jack_server_dir
);
77 // remove our custom dir
78 snprintf (dbpath
, sizeof(dbpath
), "%s/jack_db", jack_server_dir
);
84 int JackMetadata::PropertyInit()
89 char dbpath
[PATH_MAX
+ 1];
97 if ((ret
= db_env_create (&fDBenv
, 0)) != 0) {
98 jack_error ("cannot initialize DB environment: %s\n", db_strerror (ret
));
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
));
110 if ((ret
= db_create (&fDB
, fDBenv
, 0)) != 0) {
111 jack_error ("Cannot initialize metadata DB (%s)", db_strerror (ret
));
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
));
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
) {
139 return client
->PropertyChangeNotify(subject
, key
, change
);
143 void JackMetadata::MakeKeyDbt(DBT
* dbt
, jack_uuid_t subject
, const char* key
)
145 char ustr
[JACK_UUID_STRING_SIZE
];
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
160 int JackMetadata::SetProperty(JackClient
* client
, jack_uuid_t subject
, const char* key
, const char* value
, const char* type
)
168 jack_property_change_t change
;
170 if (!key
|| key
[0] == '\0') {
171 jack_error ("empty key string for metadata not allowed");
175 if (!value
|| value
[0] == '\0') {
176 jack_error ("empty value string for metadata not allowed");
180 if (PropertyInit()) {
186 MakeKeyDbt(&d_key
, subject
, key
);
190 memset (&data
, 0, sizeof(data
));
192 len1
= strlen (value
) + 1;
193 if (type
&& type
[0] != '\0') {
194 len2
= strlen (type
) + 1;
199 data
.size
= len1
+ len2
;
200 data
.data
= malloc (data
.size
);
201 memcpy (data
.data
, value
, len1
);
204 memcpy ((char *)data
.data
+ len1
, type
, len2
);
207 if (fDB
->exists (fDB
, NULL
, &d_key
, 0) == DB_NOTFOUND
) {
208 change
= PropertyCreated
;
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) {
226 PropertyChangeNotify(client
, subject
, key
, change
);
228 if (d_key
.size
> 0) {
242 int JackMetadata::GetProperty(jack_uuid_t subject
, const char* key
, char** value
, char** type
)
251 if (key
== NULL
|| key
[0] == '\0') {
255 if (PropertyInit()) {
261 MakeKeyDbt(&d_key
, subject
, key
);
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) {
283 /* result must have at least 1 char plus 1 null to be valid
285 result must have at least 2 chars plus 2 nulls to be valid
290 if (d_key
.size
> 0) {
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
);
309 /* no type specified, assume default */
313 if (d_key
.size
> 0) {
327 int JackMetadata::GetProperties(jack_uuid_t subject
, jack_description_t
* desc
)
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()) {
352 if ((ret
= fDB
->cursor (fDB
, NULL
, &cursor
, 0)) != 0) {
353 jack_error ("Cannot create cursor for metadata search (%s)", db_strerror (ret
));
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); */
375 if (memcmp (ustr
, key
.data
, JACK_UUID_STRING_SIZE
) != 0) {
377 /* if (key.size > 0) free(key.data); */
384 /* result must have at least 1 char plus 1 null to be valid
386 result must have at least 2 chars plus 2 nulls to be valid
391 /* if (key.size > 0) free(key.data); */
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 */
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
);
436 /* no type specified, assume default */
440 /* if (key.size > 0) free(key.data); */
448 cursor
->close (cursor
);
449 desc
->property_cnt
= cnt
;
458 int JackMetadata::GetAllProperties(jack_description_t
** descriptions
)
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
;
475 if (PropertyInit()) {
479 if ((ret
= fDB
->cursor (fDB
, NULL
, &cursor
, 0)) != 0) {
480 jack_error ("Cannot create cursor for metadata search (%s)", db_strerror (ret
));
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 */
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); */
506 if (jack_uuid_parse ((const char *)key
.data
, &uuid
) != 0) {
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) {
519 /* we do not have an existing description, so grow the array */
523 desc
= (jack_description_t
*)realloc (desc
, sizeof(jack_description_t
) * dsize
);
528 desc
[n
].property_size
= 0;
529 desc
[n
].property_cnt
= 0;
530 desc
[n
].properties
= NULL
;
534 jack_uuid_copy (&desc
[n
].subject
, uuid
);
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;
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
= ¤t_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
);
575 /* no type specified, assume default */
576 current_prop
->type
= NULL
;
579 /* if (key.size > 0) free(key.data); */
585 cursor
->close (cursor
);
587 (*descriptions
) = desc
;
596 int JackMetadata::GetDescription(jack_uuid_t subject
, jack_description_t
* desc
)
601 int JackMetadata::GetAllDescriptions(jack_description_t
** descs
)
606 void JackMetadata::FreeDescription(jack_description_t
* desc
, int free_actual_description_too
)
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
) {
625 int JackMetadata::RemoveProperty(JackClient
* client
, jack_uuid_t subject
, const char* key
)
632 if (PropertyInit()) {
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) {
645 PropertyChangeNotify(client
, subject
, key
, PropertyDeleted
);
647 if (d_key
.size
> 0) {
658 int JackMetadata::RemoveProperties(JackClient
* client
, jack_uuid_t subject
)
666 char ustr
[JACK_UUID_STRING_SIZE
];
670 memset (ustr
, 0, JACK_UUID_STRING_SIZE
);
671 jack_uuid_unparse (subject
, ustr
);
673 if (PropertyInit() || fDB
== NULL
) {
677 if ((ret
= fDB
->cursor (fDB
, NULL
, &cursor
, 0)) != 0) {
678 jack_error ("Cannot create cursor for metadata search (%s)", db_strerror (ret
));
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); */
700 if (memcmp (ustr
, key
.data
, JACK_UUID_STRING_SIZE
) != 0) {
702 /* if (key.size > 0) free(key.data); */
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
718 /* if (key.size > 0) free(key.data); */
724 cursor
->close (cursor
);
727 PropertyChangeNotify(client
, subject
, NULL
, PropertyDeleted
);
741 int JackMetadata::RemoveAllProperties(JackClient
* client
)
746 jack_uuid_t empty_uuid
= JACK_UUID_EMPTY_INITIALIZER
;
748 if (PropertyInit()) {
752 if ((ret
= fDB
->truncate (fDB
, NULL
, NULL
, 0)) != 0) {
753 jack_error ("Cannot clear properties (%s)", db_strerror (ret
));
757 PropertyChangeNotify(client
, empty_uuid
, NULL
, PropertyDeleted
);
766 } // end of namespace