Annotation of gnutrition/dbus.c, revision 1.1
1.1 ! asm 1: // SPDX-License-Identifier: GPL-3.0-or-later
! 2: /*
! 3: * $Id$
! 4: *
! 5: * dbus.c - D-Bus interface for GNUtrition
! 6: *
! 7: * Copyright (C) 2026 Free Software Foundation, Inc.
! 8: *
! 9: * Author: Jason Self <jself@gnu.org>
! 10: * Anton McClure <asm@gnu.org>
! 11: */
! 12:
! 13: #include "dbus.h"
! 14: #include "budget.h"
! 15: #include "db.h"
! 16: #include "log.h"
! 17: #include "i18n.h"
! 18:
! 19: #include <string.h>
! 20:
! 21: /* D-Bus introspection XML for org.gnu.gnutrition.Manager. */
! 22: static const gchar introspection_xml[] =
! 23: "<node>"
! 24: " <interface name='org.gnu.gnutrition.Manager'>"
! 25: " <method name='SearchFoods'>"
! 26: " <arg type='s' name='query' direction='in'/>"
! 27: " <arg type='a(is)' name='results' direction='out'/>"
! 28: " </method>"
! 29: " <method name='GetNutrientInfo'>"
! 30: " <arg type='i' name='food_code' direction='in'/>"
! 31: " <arg type='a{sd}' name='nutrients' direction='out'/>"
! 32: " </method>"
! 33: " <method name='GetFPEDInfo'>"
! 34: " <arg type='i' name='food_code' direction='in'/>"
! 35: " <arg type='a{sd}' name='fped' direction='out'/>"
! 36: " </method>"
! 37: " <method name='LogFood'>"
! 38: " <arg type='i' name='food_code' direction='in'/>"
! 39: " <arg type='d' name='quantity' direction='in'/>"
! 40: " <arg type='s' name='date' direction='in'/>"
! 41: " <arg type='b' name='success' direction='out'/>"
! 42: " </method>"
! 43: " <method name='GetLogForDate'>"
! 44: " <arg type='s' name='date' direction='in'/>"
! 45: " <arg type='a(ids)' name='entries' direction='out'/>"
! 46: " </method>"
! 47: " <method name='GetBudgetStatus'>"
! 48: " <arg type='s' name='date' direction='in'/>"
! 49: " <arg type='a{s(dd)}' name='status' direction='out'/>"
! 50: " </method>"
! 51: " <method name='SetUserProfile'>"
! 52: " <arg type='i' name='age' direction='in'/>"
! 53: " <arg type='d' name='height' direction='in'/>"
! 54: " <arg type='d' name='weight' direction='in'/>"
! 55: " <arg type='i' name='activity' direction='in'/>"
! 56: " <arg type='i' name='gender' direction='in'/>"
! 57: " <arg type='i' name='calorie_target' direction='out'/>"
! 58: " </method>"
! 59: " <method name='GetUserProfile'>"
! 60: " <arg type='(iddii)' name='profile' direction='out'/>"
! 61: " </method>"
! 62: " <method name='SetCalorieTarget'>"
! 63: " <arg type='i' name='calories' direction='in'/>"
! 64: " </method>"
! 65: " <method name='DeleteLogEntry'>"
! 66: " <arg type='i' name='entry_id' direction='in'/>"
! 67: " <arg type='b' name='success' direction='out'/>"
! 68: " </method>"
! 69: " <method name='UpdateLogEntry'>"
! 70: " <arg type='i' name='entry_id' direction='in'/>"
! 71: " <arg type='d' name='quantity' direction='in'/>"
! 72: " <arg type='s' name='date' direction='in'/>"
! 73: " <arg type='b' name='success' direction='out'/>"
! 74: " </method>"
! 75: " </interface>"
! 76: "</node>";
! 77:
! 78: static GDBusNodeInfo *introspection_data = NULL;
! 79:
! 80: /* Handle a SearchFoods call. */
! 81: static void
! 82: handle_search_foods (struct dbus_context *ctx,
! 83: GVariant *parameters,
! 84: GDBusMethodInvocation *invocation)
! 85: {
! 86: const gchar *query;
! 87: struct food_list results;
! 88: GVariantBuilder builder;
! 89: size_t i;
! 90:
! 91: g_variant_get (parameters, "(&s)", &query);
! 92:
! 93: if (db_search_foods (ctx->food_db, query, &results) < 0)
! 94: {
! 95: g_dbus_method_invocation_return_error (invocation,
! 96: G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
! 97: "Database search failed");
! 98: return;
! 99: }
! 100:
! 101: g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(is)"));
! 102: for (i = 0; i < results.count; i++)
! 103: g_variant_builder_add (&builder, "(is)",
! 104: results.items[i].food_code,
! 105: results.items[i].description);
! 106: food_list_free (&results);
! 107:
! 108: g_dbus_method_invocation_return_value (invocation,
! 109: g_variant_new ("(a(is))", &builder));
! 110: }
! 111:
! 112: /* Handle a GetNutrientInfo call. */
! 113: static void
! 114: handle_get_nutrient_info (struct dbus_context *ctx,
! 115: GVariant *parameters,
! 116: GDBusMethodInvocation *invocation)
! 117: {
! 118: gint food_code;
! 119: struct nutrient_list nutrients;
! 120: GVariantBuilder builder;
! 121: size_t i;
! 122:
! 123: g_variant_get (parameters, "(i)", &food_code);
! 124:
! 125: if (db_get_nutrients (ctx->food_db, food_code, &nutrients) < 0)
! 126: {
! 127: g_dbus_method_invocation_return_error (invocation,
! 128: G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
! 129: "Nutrient lookup failed");
! 130: return;
! 131: }
! 132:
! 133: g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sd}"));
! 134: for (i = 0; i < nutrients.count; i++)
! 135: g_variant_builder_add (&builder, "{sd}",
! 136: nutrients.items[i].name,
! 137: nutrients.items[i].value);
! 138: nutrient_list_free (&nutrients);
! 139:
! 140: g_dbus_method_invocation_return_value (invocation,
! 141: g_variant_new ("(a{sd})", &builder));
! 142: }
! 143:
! 144: /* Handle a GetFPEDInfo call. */
! 145: static void
! 146: handle_get_fped_info (struct dbus_context *ctx,
! 147: GVariant *parameters,
! 148: GDBusMethodInvocation *invocation)
! 149: {
! 150: gint food_code;
! 151: struct fped_entry fped;
! 152: GVariantBuilder builder;
! 153: int rc;
! 154:
! 155: g_variant_get (parameters, "(i)", &food_code);
! 156:
! 157: rc = db_get_fped (ctx->food_db, food_code, &fped);
! 158: if (rc < 0)
! 159: {
! 160: g_dbus_method_invocation_return_error (invocation,
! 161: G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
! 162: "FPED lookup failed");
! 163: return;
! 164: }
! 165:
! 166: g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sd}"));
! 167: if (rc == 0)
! 168: {
! 169: g_variant_builder_add (&builder, "{sd}", "Vegetables",
! 170: fped.vegetables);
! 171: g_variant_builder_add (&builder, "{sd}", "Fruits", fped.fruits);
! 172: g_variant_builder_add (&builder, "{sd}", "Grains", fped.grains);
! 173: g_variant_builder_add (&builder, "{sd}", "Dairy", fped.dairy);
! 174: g_variant_builder_add (&builder, "{sd}", "Protein", fped.protein);
! 175: g_variant_builder_add (&builder, "{sd}", "Oils", fped.oils);
! 176: }
! 177:
! 178: g_dbus_method_invocation_return_value (invocation,
! 179: g_variant_new ("(a{sd})", &builder));
! 180: }
! 181:
! 182: /* Handle a LogFood call. */
! 183: static void
! 184: handle_log_food (struct dbus_context *ctx,
! 185: GVariant *parameters,
! 186: GDBusMethodInvocation *invocation)
! 187: {
! 188: gint food_code;
! 189: gdouble quantity;
! 190: const gchar *date;
! 191: struct food_list results;
! 192: const char *desc;
! 193: size_t j;
! 194: gboolean success;
! 195:
! 196: g_variant_get (parameters, "(ids)", &food_code, &quantity, &date);
! 197:
! 198: /* Look up food description. */
! 199: desc = _("Unknown food");
! 200: if (db_search_foods (ctx->food_db, "", &results) == 0)
! 201: {
! 202: for (j = 0; j < results.count; j++)
! 203: {
! 204: if (results.items[j].food_code == food_code)
! 205: {
! 206: desc = results.items[j].description;
! 207: break;
! 208: }
! 209: }
! 210: }
! 211:
! 212: success = (log_add (ctx->log_db, food_code, desc, date, quantity) == 0);
! 213: food_list_free (&results);
! 214:
! 215: g_dbus_method_invocation_return_value (invocation,
! 216: g_variant_new ("(b)", success));
! 217: }
! 218:
! 219: /* Handle a GetLogForDate call. */
! 220: static void
! 221: handle_get_log_for_date (struct dbus_context *ctx,
! 222: GVariant *parameters,
! 223: GDBusMethodInvocation *invocation)
! 224: {
! 225: const gchar *date;
! 226: struct log_list entries;
! 227: GVariantBuilder builder;
! 228: size_t i;
! 229:
! 230: g_variant_get (parameters, "(&s)", &date);
! 231:
! 232: if (log_get_day (ctx->log_db, date, &entries) < 0)
! 233: {
! 234: g_dbus_method_invocation_return_error (invocation,
! 235: G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
! 236: "Log retrieval failed");
! 237: return;
! 238: }
! 239:
! 240: g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ids)"));
! 241: for (i = 0; i < entries.count; i++)
! 242: g_variant_builder_add (&builder, "(ids)",
! 243: entries.items[i].food_code,
! 244: entries.items[i].servings,
! 245: entries.items[i].description);
! 246: log_list_free (&entries);
! 247:
! 248: g_dbus_method_invocation_return_value (invocation,
! 249: g_variant_new ("(a(ids))", &builder));
! 250: }
! 251:
! 252: /* Handle a GetBudgetStatus call. */
! 253: static void
! 254: handle_get_budget_status (struct dbus_context *ctx,
! 255: GVariant *parameters,
! 256: GDBusMethodInvocation *invocation)
! 257: {
! 258: const gchar *date;
! 259: struct daily_budget budget;
! 260: struct daily_budget consumed;
! 261: struct log_list entries;
! 262: GVariantBuilder builder;
! 263: size_t i;
! 264:
! 265: g_variant_get (parameters, "(&s)", &date);
! 266:
! 267: budget = budget_for_calories (ctx->calories);
! 268: memset (&consumed, 0, sizeof consumed);
! 269:
! 270: if (log_get_day (ctx->log_db, date, &entries) == 0)
! 271: {
! 272: for (i = 0; i < entries.count; i++)
! 273: {
! 274: struct fped_entry fped;
! 275: if (db_get_fped (ctx->food_db, entries.items[i].food_code,
! 276: &fped) == 0)
! 277: {
! 278: double s = entries.items[i].servings;
! 279: consumed.vegetables += fped.vegetables * s;
! 280: consumed.fruits += fped.fruits * s;
! 281: consumed.grains += fped.grains * s;
! 282: consumed.dairy += fped.dairy * s;
! 283: consumed.protein += fped.protein * s;
! 284: consumed.oils += fped.oils * s;
! 285: }
! 286: }
! 287: log_list_free (&entries);
! 288: }
! 289:
! 290: g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{s(dd)}"));
! 291: g_variant_builder_add (&builder, "{s(dd)}", "Vegetables",
! 292: budget.vegetables, consumed.vegetables);
! 293: g_variant_builder_add (&builder, "{s(dd)}", "Fruits",
! 294: budget.fruits, consumed.fruits);
! 295: g_variant_builder_add (&builder, "{s(dd)}", "Grains",
! 296: budget.grains, consumed.grains);
! 297: g_variant_builder_add (&builder, "{s(dd)}", "Dairy",
! 298: budget.dairy, consumed.dairy);
! 299: g_variant_builder_add (&builder, "{s(dd)}", "Protein",
! 300: budget.protein, consumed.protein);
! 301: g_variant_builder_add (&builder, "{s(dd)}", "Oils",
! 302: budget.oils, consumed.oils);
! 303:
! 304: g_dbus_method_invocation_return_value (invocation,
! 305: g_variant_new ("(a{s(dd)})", &builder));
! 306: }
! 307:
! 308: /* Handle a SetUserProfile call. */
! 309: static void
! 310: handle_set_user_profile (struct dbus_context *ctx,
! 311: GVariant *parameters,
! 312: GDBusMethodInvocation *invocation)
! 313: {
! 314: gint age, activity, gender;
! 315: gdouble height, weight;
! 316: struct user_profile prof;
! 317:
! 318: g_variant_get (parameters, "(iddi)", &age, &height, &weight, &activity, &gender);
! 319:
! 320: prof.age_years = age;
! 321: prof.height_cm = height;
! 322: prof.weight_kg = weight;
! 323: prof.activity_level = activity;
! 324: prof.gender = gender;
! 325: prof.calorie_target = budget_estimate_calories (age, height, weight,
! 326: activity, gender);
! 327:
! 328: if (log_save_profile (ctx->log_db, &prof) < 0)
! 329: {
! 330: g_dbus_method_invocation_return_error (invocation,
! 331: G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
! 332: "Failed to save profile");
! 333: return;
! 334: }
! 335:
! 336: ctx->calories = prof.calorie_target;
! 337:
! 338: g_dbus_method_invocation_return_value (invocation,
! 339: g_variant_new ("(i)", prof.calorie_target));
! 340: }
! 341:
! 342: /* Handle a GetUserProfile call. */
! 343: static void
! 344: handle_get_user_profile (struct dbus_context *ctx,
! 345: GVariant *parameters,
! 346: GDBusMethodInvocation *invocation)
! 347: {
! 348: struct user_profile prof;
! 349: int rc;
! 350:
! 351: (void) parameters;
! 352:
! 353: rc = log_get_profile (ctx->log_db, &prof);
! 354: if (rc != 0)
! 355: {
! 356: /* No profile saved yet; return zeros. */
! 357: memset (&prof, 0, sizeof prof);
! 358: }
! 359:
! 360: g_dbus_method_invocation_return_value (invocation,
! 361: g_variant_new ("((iddii))",
! 362: prof.age_years,
! 363: prof.height_cm,
! 364: prof.weight_kg,
! 365: prof.activity_level,
! 366: prof.calorie_target));
! 367: }
! 368:
! 369: /* Handle a SetCalorieTarget call. */
! 370: static void
! 371: handle_set_calorie_target (struct dbus_context *ctx,
! 372: GVariant *parameters,
! 373: GDBusMethodInvocation *invocation)
! 374: {
! 375: gint cal;
! 376:
! 377: g_variant_get (parameters, "(i)", &cal);
! 378: ctx->calories = budget_round_to_pattern (cal);
! 379:
! 380: g_dbus_method_invocation_return_value (invocation, NULL);
! 381: }
! 382:
! 383: /* Handle a DeleteLogEntry call. */
! 384: static void
! 385: handle_delete_log_entry (struct dbus_context *ctx,
! 386: GVariant *parameters,
! 387: GDBusMethodInvocation *invocation)
! 388: {
! 389: gint entry_id;
! 390: gboolean success;
! 391:
! 392: g_variant_get (parameters, "(i)", &entry_id);
! 393: success = (log_delete (ctx->log_db, entry_id) == 0);
! 394:
! 395: g_dbus_method_invocation_return_value (invocation,
! 396: g_variant_new ("(b)", success));
! 397: }
! 398:
! 399: /* Handle an UpdateLogEntry call. */
! 400: static void
! 401: handle_update_log_entry (struct dbus_context *ctx,
! 402: GVariant *parameters,
! 403: GDBusMethodInvocation *invocation)
! 404: {
! 405: gint entry_id;
! 406: gdouble quantity;
! 407: const gchar *date;
! 408: gboolean success;
! 409:
! 410: g_variant_get (parameters, "(ids)", &entry_id, &quantity, &date);
! 411: success = (log_update (ctx->log_db, entry_id, date, quantity) == 0);
! 412:
! 413: g_dbus_method_invocation_return_value (invocation,
! 414: g_variant_new ("(b)", success));
! 415: }
! 416:
! 417: /* Dispatch incoming D-Bus method calls. */
! 418: static void
! 419: handle_method_call (GDBusConnection *connection,
! 420: const gchar *sender,
! 421: const gchar *object_path,
! 422: const gchar *interface_name,
! 423: const gchar *method_name,
! 424: GVariant *parameters,
! 425: GDBusMethodInvocation *invocation,
! 426: gpointer user_data)
! 427: {
! 428: struct dbus_context *ctx = user_data;
! 429:
! 430: (void) connection;
! 431: (void) sender;
! 432: (void) object_path;
! 433: (void) interface_name;
! 434:
! 435: if (g_strcmp0 (method_name, "SearchFoods") == 0)
! 436: handle_search_foods (ctx, parameters, invocation);
! 437: else if (g_strcmp0 (method_name, "GetNutrientInfo") == 0)
! 438: handle_get_nutrient_info (ctx, parameters, invocation);
! 439: else if (g_strcmp0 (method_name, "GetFPEDInfo") == 0)
! 440: handle_get_fped_info (ctx, parameters, invocation);
! 441: else if (g_strcmp0 (method_name, "LogFood") == 0)
! 442: handle_log_food (ctx, parameters, invocation);
! 443: else if (g_strcmp0 (method_name, "GetLogForDate") == 0)
! 444: handle_get_log_for_date (ctx, parameters, invocation);
! 445: else if (g_strcmp0 (method_name, "GetBudgetStatus") == 0)
! 446: handle_get_budget_status (ctx, parameters, invocation);
! 447: else if (g_strcmp0 (method_name, "SetUserProfile") == 0)
! 448: handle_set_user_profile (ctx, parameters, invocation);
! 449: else if (g_strcmp0 (method_name, "GetUserProfile") == 0)
! 450: handle_get_user_profile (ctx, parameters, invocation);
! 451: else if (g_strcmp0 (method_name, "SetCalorieTarget") == 0)
! 452: handle_set_calorie_target (ctx, parameters, invocation);
! 453: else if (g_strcmp0 (method_name, "DeleteLogEntry") == 0)
! 454: handle_delete_log_entry (ctx, parameters, invocation);
! 455: else if (g_strcmp0 (method_name, "UpdateLogEntry") == 0)
! 456: handle_update_log_entry (ctx, parameters, invocation);
! 457: else
! 458: g_dbus_method_invocation_return_error (invocation,
! 459: G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD,
! 460: "Unknown method: %s", method_name);
! 461: }
! 462:
! 463: static const GDBusInterfaceVTable interface_vtable =
! 464: {
! 465: handle_method_call,
! 466: NULL, /* get_property */
! 467: NULL, /* set_property */
! 468: { NULL }
! 469: };
! 470:
! 471: /* Called when the bus name is acquired. Register our object. */
! 472: static void
! 473: on_bus_acquired (GDBusConnection *connection,
! 474: const gchar *name,
! 475: gpointer user_data)
! 476: {
! 477: GError *error = NULL;
! 478:
! 479: (void) name;
! 480:
! 481: g_dbus_connection_register_object (connection,
! 482: "/org/gnu/gnutrition/Manager",
! 483: introspection_data->interfaces[0],
! 484: &interface_vtable,
! 485: user_data, NULL, &error);
! 486:
! 487: if (error)
! 488: {
! 489: g_printerr ("gnutrition: D-Bus register error: %s\n", error->message);
! 490: g_error_free (error);
! 491: }
! 492: }
! 493:
! 494: static void
! 495: on_name_acquired (GDBusConnection *connection,
! 496: const gchar *name,
! 497: gpointer user_data)
! 498: {
! 499: (void) connection;
! 500: (void) name;
! 501: (void) user_data;
! 502: }
! 503:
! 504: static void
! 505: on_name_lost (GDBusConnection *connection,
! 506: const gchar *name,
! 507: gpointer user_data)
! 508: {
! 509: (void) connection;
! 510: (void) user_data;
! 511: g_printerr ("gnutrition: lost D-Bus name '%s'\n", name);
! 512: }
! 513:
! 514: guint
! 515: dbus_service_start (struct dbus_context *ctx)
! 516: {
! 517: guint owner_id;
! 518:
! 519: introspection_data = g_dbus_node_info_new_for_xml (introspection_xml,
! 520: NULL);
! 521: if (!introspection_data)
! 522: {
! 523: g_printerr ("gnutrition: failed to parse D-Bus introspection XML\n");
! 524: return 0;
! 525: }
! 526:
! 527: owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
! 528: "org.gnu.gnutrition",
! 529: G_BUS_NAME_OWNER_FLAGS_NONE,
! 530: on_bus_acquired,
! 531: on_name_acquired,
! 532: on_name_lost,
! 533: ctx, NULL);
! 534: return owner_id;
! 535: }
! 536:
! 537: void
! 538: dbus_service_stop (guint owner_id)
! 539: {
! 540: if (owner_id > 0)
! 541: g_bus_unown_name (owner_id);
! 542: if (introspection_data)
! 543: {
! 544: g_dbus_node_info_unref (introspection_data);
! 545: introspection_data = NULL;
! 546: }
! 547: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>