jack1 codebase
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

723 lines
16KB

  1. /*
  2. Copyright (C) 2013 Paul Davis
  3. This program is free software; you can redistribute it and/or modify it
  4. under the terms of the GNU Lesser General Public License as published by
  5. the Free Software Foundation; either version 2.1 of the License, or (at
  6. your option) any later version.
  7. This program is distributed in the hope that it will be useful, but WITHOUT
  8. ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  9. FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
  10. License for more details.
  11. You should have received a copy of the GNU Lesser General Public License
  12. along with this program; if not, write to the Free Software Foundation,
  13. Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  14. */
  15. #include <string.h>
  16. #include <db.h>
  17. #include <limits.h>
  18. #include <jack/metadata.h>
  19. #include <jack/uuid.h>
  20. #include "internal.h"
  21. #include "local.h"
  22. const char* JACK_METADATA_PRETTY_NAME = "http://jackaudio.org/metadata/pretty-name";
  23. const char* JACK_METADATA_HARDWARE = "http://jackaudio.org/metadata/hardware";
  24. const char* JACK_METADATA_CONNECTED = "http://jackaudio.org/metadata/connected";
  25. const char* JACK_METADATA_PORT_GROUP = "http://jackaudio.org/metadata/port-group";
  26. const char* JACK_METADATA_ICON_SMALL = "http://jackaudio.org/metadata/icon-small";
  27. const char* JACK_METADATA_ICON_LARGE = "http://jackaudio.org/metadata/icon-large";
  28. static DB* db = NULL;
  29. static DB_ENV* db_env = NULL;
  30. static int
  31. jack_property_init (const char* server_name)
  32. {
  33. int ret;
  34. char dbpath[PATH_MAX + 1];
  35. char server_dir[PATH_MAX + 1];
  36. /* idempotent */
  37. if (db_env) {
  38. return 0;
  39. }
  40. if ((ret = db_env_create (&db_env, 0)) != 0) {
  41. jack_error ("cannot initialize DB environment: %s\n", db_strerror (ret));
  42. return -1;
  43. }
  44. if ((ret = db_env->open (db_env, jack_server_dir (server_name, server_dir), DB_CREATE | DB_INIT_LOCK | DB_INIT_MPOOL | DB_THREAD, 0)) != 0) {
  45. jack_error ("cannot open DB environment: %s", db_strerror (ret));
  46. return -1;
  47. }
  48. if ((ret = db_create (&db, db_env, 0)) != 0) {
  49. jack_error ("Cannot initialize metadata DB (%s)", db_strerror (ret));
  50. return -1;
  51. }
  52. snprintf (dbpath, sizeof(dbpath), "%s/%s", jack_server_dir (server_name, server_dir), "metadata.db");
  53. if ((ret = db->open (db, NULL, dbpath, NULL, DB_HASH, DB_CREATE | DB_THREAD, 0666)) != 0) {
  54. jack_error ("Cannot open metadata DB at %s: %s", dbpath, db_strerror (ret));
  55. db->close (db, 0);
  56. db = NULL;
  57. return -1;
  58. }
  59. return 0;
  60. }
  61. static void jack_properties_uninit() __attribute__ ((destructor));
  62. void
  63. jack_properties_uninit ()
  64. {
  65. if (db) {
  66. db->close (db, 0);
  67. db = NULL;
  68. }
  69. if (db_env) {
  70. db_env->close (db_env, 0);
  71. db_env = 0;
  72. }
  73. }
  74. void
  75. jack_free_description (jack_description_t* desc, int free_actual_description_too)
  76. {
  77. uint32_t n;
  78. for (n = 0; n < desc->property_cnt; ++n) {
  79. free ((char*)desc->properties[n].key);
  80. free ((char*)desc->properties[n].data);
  81. if (desc->properties[n].type) {
  82. free ((char*)desc->properties[n].type);
  83. }
  84. }
  85. free (desc->properties);
  86. if (free_actual_description_too) {
  87. free (desc);
  88. }
  89. }
  90. static int
  91. jack_property_change_notify (jack_client_t* client, jack_uuid_t uuid, const char* key, jack_property_change_t change)
  92. {
  93. jack_request_t req;
  94. /* the engine passes in a NULL client when it removes metadata during port or client removal
  95. */
  96. if (client == NULL) {
  97. return 0;
  98. }
  99. req.type = PropertyChangeNotify;
  100. req.x.property.change = change;
  101. jack_uuid_copy (&req.x.property.uuid, uuid);
  102. req.x.property.keylen = key ? strlen (key) + 1 : 0;
  103. req.x.property.key = key;
  104. return jack_client_deliver_request (client, &req);
  105. }
  106. static void
  107. make_key_dbt (DBT* dbt, jack_uuid_t subject, const char* key)
  108. {
  109. char ustr[JACK_UUID_STRING_SIZE];
  110. size_t len1, len2;
  111. memset (dbt, 0, sizeof(DBT));
  112. memset (ustr, 0, JACK_UUID_STRING_SIZE);
  113. jack_uuid_unparse (subject, ustr);
  114. len1 = JACK_UUID_STRING_SIZE;
  115. len2 = strlen (key) + 1;
  116. dbt->size = len1 + len2;
  117. dbt->data = malloc (dbt->size);
  118. memcpy (dbt->data, ustr, len1); // copy subject+null
  119. memcpy (dbt->data + len1, key, len2); // copy key+null
  120. }
  121. int
  122. jack_set_property (jack_client_t* client,
  123. jack_uuid_t subject,
  124. const char* key,
  125. const char* value,
  126. const char* type)
  127. {
  128. DBT d_key;
  129. DBT data;
  130. int ret;
  131. size_t len1, len2;
  132. jack_property_change_t change;
  133. if (!key || key[0] == '\0') {
  134. jack_error ("empty key string for metadata not allowed");
  135. return -1;
  136. }
  137. if (!value || value[0] == '\0') {
  138. jack_error ("empty value string for metadata not allowed");
  139. return -1;
  140. }
  141. if (jack_property_init (NULL)) {
  142. return -1;
  143. }
  144. /* build a key */
  145. make_key_dbt (&d_key, subject, key);
  146. /* build data */
  147. memset (&data, 0, sizeof(data));
  148. len1 = strlen (value) + 1;
  149. if (type && type[0] != '\0') {
  150. len2 = strlen (type) + 1;
  151. } else {
  152. len2 = 0;
  153. }
  154. data.size = len1 + len2;
  155. data.data = malloc (data.size);
  156. memcpy (data.data, value, len1);
  157. if (len2) {
  158. memcpy (data.data + len1, type, len2);
  159. }
  160. if (db->exists (db, NULL, &d_key, 0) == DB_NOTFOUND) {
  161. change = PropertyCreated;
  162. } else {
  163. change = PropertyChanged;
  164. }
  165. if ((ret = db->put (db, NULL, &d_key, &data, 0)) != 0) {
  166. char ustr[JACK_UUID_STRING_SIZE];
  167. jack_uuid_unparse (subject, ustr);
  168. jack_error ("Cannot store metadata for %s/%s (%s)", ustr, key, db_strerror (ret));
  169. if (d_key.size > 0) {
  170. free (d_key.data);
  171. }
  172. if (data.size > 0) {
  173. free (data.data);
  174. }
  175. return -1;
  176. }
  177. jack_property_change_notify (client, subject, key, change);
  178. if (d_key.size > 0) {
  179. free (d_key.data);
  180. }
  181. if (data.size > 0) {
  182. free (data.data);
  183. }
  184. return 0;
  185. }
  186. int
  187. jack_get_property (jack_uuid_t subject,
  188. const char* key,
  189. char** value,
  190. char** type)
  191. {
  192. DBT d_key;
  193. DBT data;
  194. int ret;
  195. size_t len1, len2;
  196. if (key == NULL || key[0] == '\0') {
  197. return -1;
  198. }
  199. if (jack_property_init (NULL)) {
  200. return -1;
  201. }
  202. /* build a key */
  203. make_key_dbt (&d_key, subject, key);
  204. /* setup data DBT */
  205. memset (&data, 0, sizeof(data));
  206. data.flags = DB_DBT_MALLOC;
  207. if ((ret = db->get (db, NULL, &d_key, &data, 0)) != 0) {
  208. if (ret != DB_NOTFOUND) {
  209. char ustr[JACK_UUID_STRING_SIZE];
  210. jack_uuid_unparse (subject, ustr);
  211. jack_error ("Cannot metadata for %s/%s (%s)", ustr, key, db_strerror (ret));
  212. }
  213. if (d_key.size > 0) {
  214. free (d_key.data);
  215. }
  216. if (data.size > 0) {
  217. free (data.data);
  218. }
  219. return -1;
  220. }
  221. /* result must have at least 2 chars plus 2 nulls to be valid
  222. */
  223. if (data.size < 4) {
  224. if (d_key.size > 0) {
  225. free (d_key.data);
  226. }
  227. if (data.size > 0) {
  228. free (data.data);
  229. }
  230. return -1;
  231. }
  232. len1 = strlen (data.data) + 1;
  233. (*value) = (char*)malloc (len1);
  234. memcpy (*value, data.data, len1);
  235. if (len1 < data.size) {
  236. len2 = strlen (data.data + len1) + 1;
  237. (*type) = (char*)malloc (len2);
  238. memcpy (*type, data.data + len1, len2);
  239. } else {
  240. /* no type specified, assume default */
  241. *type = NULL;
  242. }
  243. if (d_key.size > 0) {
  244. free (d_key.data);
  245. }
  246. if (data.size > 0) {
  247. free (data.data);
  248. }
  249. return 0;
  250. }
  251. int
  252. jack_get_properties (jack_uuid_t subject,
  253. jack_description_t* desc)
  254. {
  255. DBT key;
  256. DBT data;
  257. DBC* cursor;
  258. int ret;
  259. size_t len1, len2;
  260. size_t cnt = 0;
  261. char ustr[JACK_UUID_STRING_SIZE];
  262. size_t props_size = 0;
  263. jack_property_t* prop;
  264. desc->properties = NULL;
  265. desc->property_cnt = 0;
  266. memset (ustr, 0, JACK_UUID_STRING_SIZE);
  267. jack_uuid_unparse (subject, ustr);
  268. if (jack_property_init (NULL)) {
  269. return -1;
  270. }
  271. if ((ret = db->cursor (db, NULL, &cursor, 0)) != 0) {
  272. jack_error ("Cannot create cursor for metadata search (%s)", db_strerror (ret));
  273. return -1;
  274. }
  275. memset (&key, 0, sizeof(key));
  276. memset (&data, 0, sizeof(data));
  277. data.flags = DB_DBT_MALLOC;
  278. while ((ret = cursor->get (cursor, &key, &data, DB_NEXT)) == 0) {
  279. /* require 2 extra chars (data+null) for key,
  280. which is composed of UUID str plus a key name
  281. */
  282. if (key.size < JACK_UUID_STRING_SIZE + 2) {
  283. /* if (key.size > 0) free(key.data); */
  284. if (data.size > 0) {
  285. free (data.data);
  286. }
  287. continue;
  288. }
  289. if (memcmp (ustr, key.data, JACK_UUID_STRING_SIZE) != 0) {
  290. /* not relevant */
  291. /* if (key.size > 0) free(key.data); */
  292. if (data.size > 0) {
  293. free (data.data);
  294. }
  295. continue;
  296. }
  297. /* result must have at least 2 chars plus 2 nulls to be valid
  298. */
  299. if (data.size < 4) {
  300. /* if (key.size > 0) free(key.data); */
  301. if (data.size > 0) {
  302. free (data.data);
  303. }
  304. continue;
  305. }
  306. /* realloc array if necessary */
  307. if (cnt == props_size) {
  308. if (props_size == 0) {
  309. props_size = 8; /* a rough guess at a likely upper bound for the number of properties */
  310. } else {
  311. props_size *= 2;
  312. }
  313. desc->properties = (jack_property_t*)realloc (desc->properties, sizeof(jack_property_t) * props_size);
  314. }
  315. prop = &desc->properties[cnt];
  316. /* store UUID/subject */
  317. jack_uuid_copy (&desc->subject, subject);
  318. /* copy key (without leading UUID as subject */
  319. len1 = key.size - JACK_UUID_STRING_SIZE;
  320. prop->key = malloc (len1);
  321. memcpy ((char*)prop->key, key.data + JACK_UUID_STRING_SIZE, len1);
  322. /* copy data (which contains 1 or 2 null terminated strings, the value
  323. and optionally a MIME type.
  324. */
  325. len1 = strlen (data.data) + 1;
  326. prop->data = (char*)malloc (len1);
  327. memcpy ((char*)prop->data, data.data, len1);
  328. if (len1 < data.size) {
  329. len2 = strlen (data.data + len1) + 1;
  330. prop->type = (char*)malloc (len2);
  331. memcpy ((char*)prop->type, data.data + len1, len2);
  332. } else {
  333. /* no type specified, assume default */
  334. prop->type = NULL;
  335. }
  336. /* if (key.size > 0) free(key.data); */
  337. if (data.size > 0) {
  338. free (data.data);
  339. }
  340. ++cnt;
  341. }
  342. cursor->close (cursor);
  343. desc->property_cnt = cnt;
  344. return cnt;
  345. }
  346. int
  347. jack_get_all_properties (jack_description_t** descriptions)
  348. {
  349. DBT key;
  350. DBT data;
  351. DBC* cursor;
  352. int ret;
  353. size_t dcnt = 0;
  354. size_t dsize = 0;
  355. size_t n = 0;
  356. jack_description_t* desc = NULL;
  357. jack_uuid_t uuid = JACK_UUID_EMPTY_INITIALIZER;
  358. jack_description_t* current_desc = NULL;
  359. jack_property_t* current_prop = NULL;
  360. size_t len1, len2;
  361. if (jack_property_init (NULL)) {
  362. return -1;
  363. }
  364. if ((ret = db->cursor (db, NULL, &cursor, 0)) != 0) {
  365. jack_error ("Cannot create cursor for metadata search (%s)", db_strerror (ret));
  366. return -1;
  367. }
  368. memset (&key, 0, sizeof(key));
  369. memset (&data, 0, sizeof(data));
  370. data.flags = DB_DBT_MALLOC;
  371. dsize = 8; /* initial guess at number of descriptions we need */
  372. dcnt = 0;
  373. desc = (jack_description_t*)malloc (dsize * sizeof(jack_description_t));
  374. while ((ret = cursor->get (cursor, &key, &data, DB_NEXT)) == 0) {
  375. /* require 2 extra chars (data+null) for key,
  376. which is composed of UUID str plus a key name
  377. */
  378. if (key.size < JACK_UUID_STRING_SIZE + 2) {
  379. /* if (key.size > 0) free(key.data); */
  380. if (data.size > 0) {
  381. free (data.data);
  382. }
  383. continue;
  384. }
  385. if (jack_uuid_parse (key.data, &uuid) != 0) {
  386. continue;
  387. }
  388. /* do we have an existing description for this UUID */
  389. for (n = 0; n < dcnt; ++n) {
  390. if (jack_uuid_compare (uuid, desc[n].subject) == 0) {
  391. break;
  392. }
  393. }
  394. if (n == dcnt) {
  395. /* we do not have an existing description, so grow the array */
  396. if (dcnt == dsize) {
  397. dsize *= 2;
  398. desc = (jack_description_t*)realloc (desc, sizeof(jack_description_t) * dsize);
  399. }
  400. /* initialize */
  401. desc[n].property_size = 0;
  402. desc[n].property_cnt = 0;
  403. desc[n].properties = NULL;
  404. /* set up UUID */
  405. jack_uuid_copy (&desc[n].subject, uuid);
  406. dcnt++;
  407. }
  408. current_desc = &desc[n];
  409. /* see if there is room for the new property or if we need to realloc
  410. */
  411. if (current_desc->property_cnt == current_desc->property_size) {
  412. if (current_desc->property_size == 0) {
  413. current_desc->property_size = 8;
  414. } else {
  415. current_desc->property_size *= 2;
  416. }
  417. current_desc->properties = (jack_property_t*)realloc (current_desc->properties, sizeof(jack_property_t) * current_desc->property_size);
  418. }
  419. current_prop = &current_desc->properties[current_desc->property_cnt++];
  420. /* copy key (without leading UUID) */
  421. len1 = key.size - JACK_UUID_STRING_SIZE;
  422. current_prop->key = malloc (len1);
  423. memcpy ((char*)current_prop->key, key.data + JACK_UUID_STRING_SIZE, len1);
  424. /* copy data (which contains 1 or 2 null terminated strings, the value
  425. and optionally a MIME type.
  426. */
  427. len1 = strlen (data.data) + 1;
  428. current_prop->data = (char*)malloc (len1);
  429. memcpy ((char*)current_prop->data, data.data, len1);
  430. if (len1 < data.size) {
  431. len2 = strlen (data.data + len1) + 1;
  432. current_prop->type = (char*)malloc (len2);
  433. memcpy ((char*)current_prop->type, data.data + len1, len2);
  434. } else {
  435. /* no type specified, assume default */
  436. current_prop->type = NULL;
  437. }
  438. /* if (key.size > 0) free(key.data); */
  439. if (data.size > 0) {
  440. free (data.data);
  441. }
  442. }
  443. cursor->close (cursor);
  444. (*descriptions) = desc;
  445. return dcnt;
  446. }
  447. int
  448. jack_get_description (jack_uuid_t subject,
  449. jack_description_t* desc)
  450. {
  451. return 0;
  452. }
  453. int
  454. jack_get_all_descriptions (jack_description_t** descs)
  455. {
  456. return 0;
  457. }
  458. int
  459. jack_set_property_change_callback (jack_client_t *client,
  460. JackPropertyChangeCallback callback, void *arg)
  461. {
  462. if (client->control->active) {
  463. jack_error ("You cannot set callbacks on an active client.");
  464. return -1;
  465. }
  466. client->property_cb = callback;
  467. client->property_cb_arg = arg;
  468. client->control->property_cbset = (callback != NULL);
  469. return 0;
  470. }
  471. int
  472. jack_remove_property (jack_client_t* client, jack_uuid_t subject, const char* key)
  473. {
  474. DBT d_key;
  475. int ret;
  476. if (jack_property_init (NULL)) {
  477. return -1;
  478. }
  479. make_key_dbt (&d_key, subject, key);
  480. if ((ret = db->del (db, NULL, &d_key, 0)) != 0) {
  481. jack_error ("Cannot delete key %s (%s)", key, db_strerror (ret));
  482. if (d_key.size > 0) {
  483. free (d_key.data);
  484. }
  485. return -1;
  486. }
  487. jack_property_change_notify (client, subject, key, PropertyDeleted);
  488. if (d_key.size > 0) {
  489. free (d_key.data);
  490. }
  491. return 0;
  492. }
  493. int
  494. jack_remove_properties (jack_client_t* client, jack_uuid_t subject)
  495. {
  496. DBT key;
  497. DBT data;
  498. DBC* cursor;
  499. int ret;
  500. char ustr[JACK_UUID_STRING_SIZE];
  501. int retval = 0;
  502. uint32_t cnt = 0;
  503. memset (ustr, 0, JACK_UUID_STRING_SIZE);
  504. jack_uuid_unparse (subject, ustr);
  505. if (jack_property_init (NULL)) {
  506. return -1;
  507. }
  508. if ((ret = db->cursor (db, NULL, &cursor, 0)) != 0) {
  509. jack_error ("Cannot create cursor for metadata search (%s)", db_strerror (ret));
  510. return -1;
  511. }
  512. memset (&key, 0, sizeof(key));
  513. memset (&data, 0, sizeof(data));
  514. data.flags = DB_DBT_MALLOC;
  515. while ((ret = cursor->get (cursor, &key, &data, DB_NEXT)) == 0) {
  516. /* require 2 extra chars (data+null) for key,
  517. which is composed of UUID str plus a key name
  518. */
  519. if (key.size < JACK_UUID_STRING_SIZE + 2) {
  520. /* if (key.size > 0) free(key.data); */
  521. if (data.size > 0) {
  522. free (data.data);
  523. }
  524. continue;
  525. }
  526. if (memcmp (ustr, key.data, JACK_UUID_STRING_SIZE) != 0) {
  527. /* not relevant */
  528. /* if (key.size > 0) free(key.data); */
  529. if (data.size > 0) {
  530. free (data.data);
  531. }
  532. continue;
  533. }
  534. if ((ret = cursor->del (cursor, 0)) != 0) {
  535. jack_error ("cannot delete property (%s)", db_strerror (ret));
  536. /* don't return -1 here since this would leave things
  537. even more inconsistent. wait till the cursor is finished
  538. */
  539. retval = -1;
  540. }
  541. cnt++;
  542. /* if (key.size > 0) free(key.data); */
  543. if (data.size > 0) {
  544. free (data.data);
  545. }
  546. }
  547. cursor->close (cursor);
  548. if (cnt) {
  549. jack_property_change_notify (client, subject, NULL, PropertyDeleted);
  550. }
  551. if (retval) {
  552. return -1;
  553. }
  554. return cnt;
  555. }
  556. int
  557. jack_remove_all_properties (jack_client_t* client)
  558. {
  559. int ret;
  560. jack_uuid_t empty_uuid = JACK_UUID_EMPTY_INITIALIZER;
  561. if (jack_property_init (NULL)) {
  562. return -1;
  563. }
  564. if ((ret = db->truncate (db, NULL, NULL, 0)) != 0) {
  565. jack_error ("Cannot clear properties (%s)", db_strerror (ret));
  566. return -1;
  567. }
  568. jack_property_change_notify (client, empty_uuid, NULL, PropertyDeleted);
  569. return 0;
  570. }