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>