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"
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";
39 JackMetadata::JackMetadata(const char* server_name
)
41 : fDB(NULL
), fDBenv(NULL
)
44 PropertyInit(server_name
);
47 JackMetadata::~JackMetadata()
55 fDBenv
->close (fDBenv
, 0);
61 int JackMetadata::PropertyInit(const char* server_name
)
66 char dbpath
[PATH_MAX
+ 1];
74 if ((ret
= db_env_create (&fDBenv
, 0)) != 0) {
75 jack_error ("cannot initialize DB environment: %s\n", db_strerror (ret
));
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
));
84 if ((ret
= db_create (&fDB
, fDBenv
, 0)) != 0) {
85 jack_error ("Cannot initialize metadata DB (%s)", db_strerror (ret
));
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
));
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
) {
114 return client
->PropertyChangeNotify(subject
, key
, change
);
118 void JackMetadata::MakeKeyDbt(DBT
* dbt
, jack_uuid_t subject
, const char* key
)
120 char ustr
[JACK_UUID_STRING_SIZE
];
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
135 int JackMetadata::SetProperty(JackClient
* client
, jack_uuid_t subject
, const char* key
, const char* value
, const char* type
)
143 jack_property_change_t change
;
145 if (!key
|| key
[0] == '\0') {
146 jack_error ("empty key string for metadata not allowed");
150 if (!value
|| value
[0] == '\0') {
151 jack_error ("empty value string for metadata not allowed");
155 if (PropertyInit(NULL
)) {
161 MakeKeyDbt(&d_key
, subject
, key
);
165 memset (&data
, 0, sizeof(data
));
167 len1
= strlen (value
) + 1;
168 if (type
&& type
[0] != '\0') {
169 len2
= strlen (type
) + 1;
174 data
.size
= len1
+ len2
;
175 data
.data
= malloc (data
.size
);
176 memcpy (data
.data
, value
, len1
);
179 memcpy ((char *)data
.data
+ len1
, type
, len2
);
182 if (fDB
->exists (fDB
, NULL
, &d_key
, 0) == DB_NOTFOUND
) {
183 change
= PropertyCreated
;
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) {
201 PropertyChangeNotify(client
, subject
, key
, change
);
203 if (d_key
.size
> 0) {
217 int JackMetadata::GetProperty(jack_uuid_t subject
, const char* key
, char** value
, char** type
)
226 if (key
== NULL
|| key
[0] == '\0') {
230 if (PropertyInit(NULL
)) {
236 MakeKeyDbt(&d_key
, subject
, key
);
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) {
258 /* result must have at least 1 char plus 1 null to be valid
260 result must have at least 2 chars plus 2 nulls to be valid
265 if (d_key
.size
> 0) {
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
);
284 /* no type specified, assume default */
288 if (d_key
.size
> 0) {
302 int JackMetadata::GetProperties(jack_uuid_t subject
, jack_description_t
* desc
)
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
)) {
327 if ((ret
= fDB
->cursor (fDB
, NULL
, &cursor
, 0)) != 0) {
328 jack_error ("Cannot create cursor for metadata search (%s)", db_strerror (ret
));
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); */
350 if (memcmp (ustr
, key
.data
, JACK_UUID_STRING_SIZE
) != 0) {
352 /* if (key.size > 0) free(key.data); */
359 /* result must have at least 1 char plus 1 null to be valid
361 result must have at least 2 chars plus 2 nulls to be valid
366 /* if (key.size > 0) free(key.data); */
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 */
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
);
411 /* no type specified, assume default */
415 /* if (key.size > 0) free(key.data); */
423 cursor
->close (cursor
);
424 desc
->property_cnt
= cnt
;
433 int JackMetadata::GetAllProperties(jack_description_t
** descriptions
)
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
;
450 if (PropertyInit(NULL
)) {
454 if ((ret
= fDB
->cursor (fDB
, NULL
, &cursor
, 0)) != 0) {
455 jack_error ("Cannot create cursor for metadata search (%s)", db_strerror (ret
));
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 */
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); */
481 if (jack_uuid_parse ((const char *)key
.data
, &uuid
) != 0) {
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) {
494 /* we do not have an existing description, so grow the array */
498 desc
= (jack_description_t
*)realloc (desc
, sizeof(jack_description_t
) * dsize
);
503 desc
[n
].property_size
= 0;
504 desc
[n
].property_cnt
= 0;
505 desc
[n
].properties
= NULL
;
509 jack_uuid_copy (&desc
[n
].subject
, uuid
);
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;
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
= ¤t_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
);
550 /* no type specified, assume default */
551 current_prop
->type
= NULL
;
554 /* if (key.size > 0) free(key.data); */
560 cursor
->close (cursor
);
562 (*descriptions
) = desc
;
571 int JackMetadata::GetDescription(jack_uuid_t subject
, jack_description_t
* desc
)
576 int JackMetadata::GetAllDescriptions(jack_description_t
** descs
)
581 void JackMetadata::FreeDescription(jack_description_t
* desc
, int free_actual_description_too
)
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
) {
600 int JackMetadata::RemoveProperty(JackClient
* client
, jack_uuid_t subject
, const char* key
)
607 if (PropertyInit(NULL
)) {
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) {
620 PropertyChangeNotify(client
, subject
, key
, PropertyDeleted
);
622 if (d_key
.size
> 0) {
633 int JackMetadata::RemoveProperties(JackClient
* client
, jack_uuid_t subject
)
641 char ustr
[JACK_UUID_STRING_SIZE
];
645 memset (ustr
, 0, JACK_UUID_STRING_SIZE
);
646 jack_uuid_unparse (subject
, ustr
);
648 if (PropertyInit(NULL
)) {
652 if ((ret
= fDB
->cursor (fDB
, NULL
, &cursor
, 0)) != 0) {
653 jack_error ("Cannot create cursor for metadata search (%s)", db_strerror (ret
));
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); */
675 if (memcmp (ustr
, key
.data
, JACK_UUID_STRING_SIZE
) != 0) {
677 /* if (key.size > 0) free(key.data); */
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
693 /* if (key.size > 0) free(key.data); */
699 cursor
->close (cursor
);
702 PropertyChangeNotify(client
, subject
, NULL
, PropertyDeleted
);
716 int JackMetadata::RemoveAllProperties(JackClient
* client
)
721 jack_uuid_t empty_uuid
= JACK_UUID_EMPTY_INITIALIZER
;
723 if (PropertyInit(NULL
)) {
727 if ((ret
= fDB
->truncate (fDB
, NULL
, NULL
, 0)) != 0) {
728 jack_error ("Cannot clear properties (%s)", db_strerror (ret
));
732 PropertyChangeNotify(client
, empty_uuid
, NULL
, PropertyDeleted
);
741 } // end of namespace