Annotation of gnutrition/log.c, revision 1.1

1.1     ! asm         1: // SPDX-License-Identifier: GPL-3.0-or-later
        !             2: /*
        !             3:  * $Id$
        !             4:  *
        !             5:  * log.c - Food log 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 "log.h"
        !            14: #include "i18n.h"
        !            15: 
        !            16: #include <stdio.h>
        !            17: #include <stdlib.h>
        !            18: #include <string.h>
        !            19: 
        !            20: #define INITIAL_CAPACITY 32
        !            21: 
        !            22: sqlite3 *
        !            23: log_open (const char *path)
        !            24: {
        !            25:   sqlite3 *db;
        !            26:   int rc;
        !            27:   char *errmsg;
        !            28: 
        !            29:   rc = sqlite3_open (path, &db);
        !            30:   if (rc != SQLITE_OK)
        !            31:     {
        !            32:       fprintf (stderr, _("gnutrition: cannot open log database '%s': %s\n"),
        !            33:                path, sqlite3_errmsg (db));
        !            34:       sqlite3_close (db);
        !            35:       return NULL;
        !            36:     }
        !            37: 
        !            38:   /* Create the log table if it does not exist.  */
        !            39:   rc = sqlite3_exec (db,
        !            40:     "CREATE TABLE IF NOT EXISTS food_log ("
        !            41:     "  id INTEGER PRIMARY KEY AUTOINCREMENT,"
        !            42:     "  food_code INTEGER NOT NULL,"
        !            43:     "  description TEXT NOT NULL,"
        !            44:     "  date TEXT NOT NULL,"
        !            45:     "  servings REAL NOT NULL DEFAULT 1.0"
        !            46:     ")",
        !            47:     NULL, NULL, &errmsg);
        !            48: 
        !            49:   if (rc != SQLITE_OK)
        !            50:     {
        !            51:       fprintf (stderr, _("gnutrition: cannot create log table: %s\n"), errmsg);
        !            52:       sqlite3_free (errmsg);
        !            53:       sqlite3_close (db);
        !            54:       return NULL;
        !            55:     }
        !            56: 
        !            57:   /* Create the profile table if it does not exist.  Only one row
        !            58:      is ever stored (id = 1).  */
        !            59:   rc = sqlite3_exec (db,
        !            60:     "CREATE TABLE IF NOT EXISTS user_profile ("
        !            61:     "  id INTEGER PRIMARY KEY CHECK (id = 1),"
        !            62:     "  age_years INTEGER NOT NULL,"
        !            63:     "  height_cm REAL NOT NULL,"
        !            64:     "  weight_kg REAL NOT NULL,"
        !            65:     "  activity_level INTEGER NOT NULL,"
        !            66:     "  gender INTEGER NOT NULL,"
        !            67:     "  calorie_target INTEGER NOT NULL"
        !            68:     ")",
        !            69:     NULL, NULL, &errmsg);
        !            70: 
        !            71:   if (rc != SQLITE_OK)
        !            72:     {
        !            73:       fprintf (stderr, _("gnutrition: cannot create profile table: %s\n"),
        !            74:                errmsg);
        !            75:       sqlite3_free (errmsg);
        !            76:       sqlite3_close (db);
        !            77:       return NULL;
        !            78:     }
        !            79: 
        !            80:   return db;
        !            81: }
        !            82: 
        !            83: void
        !            84: log_close (sqlite3 *db)
        !            85: {
        !            86:   if (db)
        !            87:     sqlite3_close (db);
        !            88: }
        !            89: 
        !            90: int
        !            91: log_add (sqlite3 *db, int food_code, const char *description,
        !            92:          const char *date, double servings)
        !            93: {
        !            94:   sqlite3_stmt *stmt;
        !            95:   int rc;
        !            96: 
        !            97:   rc = sqlite3_prepare_v2 (db,
        !            98:     "INSERT INTO food_log (food_code, description, date, servings) "
        !            99:     "VALUES (?1, ?2, ?3, ?4)",
        !           100:     -1, &stmt, NULL);
        !           101: 
        !           102:   if (rc != SQLITE_OK)
        !           103:     {
        !           104:       fprintf (stderr, _("gnutrition: SQL error: %s\n"), sqlite3_errmsg (db));
        !           105:       return -1;
        !           106:     }
        !           107: 
        !           108:   sqlite3_bind_int (stmt, 1, food_code);
        !           109:   sqlite3_bind_text (stmt, 2, description, -1, SQLITE_TRANSIENT);
        !           110:   sqlite3_bind_text (stmt, 3, date, -1, SQLITE_TRANSIENT);
        !           111:   sqlite3_bind_double (stmt, 4, servings);
        !           112: 
        !           113:   rc = sqlite3_step (stmt);
        !           114:   sqlite3_finalize (stmt);
        !           115: 
        !           116:   if (rc != SQLITE_DONE)
        !           117:     {
        !           118:       fprintf (stderr, _("gnutrition: cannot add log entry: %s\n"),
        !           119:                sqlite3_errmsg (db));
        !           120:       return -1;
        !           121:     }
        !           122: 
        !           123:   return 0;
        !           124: }
        !           125: 
        !           126: int
        !           127: log_delete (sqlite3 *db, int id)
        !           128: {
        !           129:   sqlite3_stmt *stmt;
        !           130:   int rc;
        !           131: 
        !           132:   rc = sqlite3_prepare_v2 (db,
        !           133:     "DELETE FROM food_log WHERE id = ?1",
        !           134:     -1, &stmt, NULL);
        !           135: 
        !           136:   if (rc != SQLITE_OK)
        !           137:     {
        !           138:       fprintf (stderr, _("gnutrition: SQL error: %s\n"), sqlite3_errmsg (db));
        !           139:       return -1;
        !           140:     }
        !           141: 
        !           142:   sqlite3_bind_int (stmt, 1, id);
        !           143: 
        !           144:   rc = sqlite3_step (stmt);
        !           145:   sqlite3_finalize (stmt);
        !           146: 
        !           147:   if (rc != SQLITE_DONE)
        !           148:     {
        !           149:       fprintf (stderr, _("gnutrition: cannot delete log entry: %s\n"),
        !           150:                sqlite3_errmsg (db));
        !           151:       return -1;
        !           152:     }
        !           153: 
        !           154:   return 0;
        !           155: }
        !           156: 
        !           157: int
        !           158: log_update (sqlite3 *db, int id, const char *date, double servings)
        !           159: {
        !           160:   sqlite3_stmt *stmt;
        !           161:   int rc;
        !           162: 
        !           163:   rc = sqlite3_prepare_v2 (db,
        !           164:     "UPDATE food_log SET date = ?1, servings = ?2 WHERE id = ?3",
        !           165:     -1, &stmt, NULL);
        !           166: 
        !           167:   if (rc != SQLITE_OK)
        !           168:     {
        !           169:       fprintf (stderr, _("gnutrition: SQL error: %s\n"), sqlite3_errmsg (db));
        !           170:       return -1;
        !           171:     }
        !           172: 
        !           173:   sqlite3_bind_text (stmt, 1, date, -1, SQLITE_TRANSIENT);
        !           174:   sqlite3_bind_double (stmt, 2, servings);
        !           175:   sqlite3_bind_int (stmt, 3, id);
        !           176: 
        !           177:   rc = sqlite3_step (stmt);
        !           178:   sqlite3_finalize (stmt);
        !           179: 
        !           180:   if (rc != SQLITE_DONE)
        !           181:     {
        !           182:       fprintf (stderr, _("gnutrition: cannot update log entry: %s\n"),
        !           183:                sqlite3_errmsg (db));
        !           184:       return -1;
        !           185:     }
        !           186: 
        !           187:   return 0;
        !           188: }
        !           189: 
        !           190: /* Append a log_entry to LIST, growing dynamically.  */
        !           191: static int
        !           192: log_list_append (struct log_list *list, int id, int food_code,
        !           193:                  const char *description, const char *date, double servings)
        !           194: {
        !           195:   if (list->count >= list->capacity)
        !           196:     {
        !           197:       size_t new_cap;
        !           198:       struct log_entry *tmp;
        !           199: 
        !           200:       new_cap = list->capacity == 0 ? INITIAL_CAPACITY : list->capacity * 2;
        !           201:       tmp = realloc (list->items, new_cap * sizeof *tmp);
        !           202:       if (!tmp)
        !           203:         {
        !           204:           fprintf (stderr, _("gnutrition: memory exhausted\n"));
        !           205:           return -1;
        !           206:         }
        !           207:       list->items = tmp;
        !           208:       list->capacity = new_cap;
        !           209:     }
        !           210: 
        !           211:   list->items[list->count].id = id;
        !           212:   list->items[list->count].food_code = food_code;
        !           213:   list->items[list->count].description = strdup (description);
        !           214:   list->items[list->count].date = strdup (date);
        !           215:   if (!list->items[list->count].description
        !           216:       || !list->items[list->count].date)
        !           217:     {
        !           218:       fprintf (stderr, _("gnutrition: memory exhausted\n"));
        !           219:       return -1;
        !           220:     }
        !           221:   list->items[list->count].servings = servings;
        !           222:   list->count++;
        !           223:   return 0;
        !           224: }
        !           225: 
        !           226: int
        !           227: log_get_day (sqlite3 *db, const char *date, struct log_list *results)
        !           228: {
        !           229:   sqlite3_stmt *stmt;
        !           230:   int rc;
        !           231: 
        !           232:   results->items = NULL;
        !           233:   results->count = 0;
        !           234:   results->capacity = 0;
        !           235: 
        !           236:   rc = sqlite3_prepare_v2 (db,
        !           237:     "SELECT id, food_code, description, date, servings "
        !           238:     "FROM food_log "
        !           239:     "WHERE date = ?1 "
        !           240:     "ORDER BY id",
        !           241:     -1, &stmt, NULL);
        !           242: 
        !           243:   if (rc != SQLITE_OK)
        !           244:     {
        !           245:       fprintf (stderr, _("gnutrition: SQL error: %s\n"), sqlite3_errmsg (db));
        !           246:       return -1;
        !           247:     }
        !           248: 
        !           249:   sqlite3_bind_text (stmt, 1, date, -1, SQLITE_TRANSIENT);
        !           250: 
        !           251:   while ((rc = sqlite3_step (stmt)) == SQLITE_ROW)
        !           252:     {
        !           253:       int id = sqlite3_column_int (stmt, 0);
        !           254:       int food_code = sqlite3_column_int (stmt, 1);
        !           255:       const char *desc = (const char *) sqlite3_column_text (stmt, 2);
        !           256:       const char *d = (const char *) sqlite3_column_text (stmt, 3);
        !           257:       double servings = sqlite3_column_double (stmt, 4);
        !           258:       if (log_list_append (results, id, food_code,
        !           259:                            desc ? desc : "", d ? d : "", servings) < 0)
        !           260:         {
        !           261:           sqlite3_finalize (stmt);
        !           262:           log_list_free (results);
        !           263:           return -1;
        !           264:         }
        !           265:     }
        !           266: 
        !           267:   sqlite3_finalize (stmt);
        !           268: 
        !           269:   if (rc != SQLITE_DONE)
        !           270:     {
        !           271:       fprintf (stderr, _("gnutrition: SQL error: %s\n"), sqlite3_errmsg (db));
        !           272:       log_list_free (results);
        !           273:       return -1;
        !           274:     }
        !           275: 
        !           276:   return 0;
        !           277: }
        !           278: 
        !           279: int
        !           280: log_save_profile (sqlite3 *db, const struct user_profile *profile)
        !           281: {
        !           282:   sqlite3_stmt *stmt;
        !           283:   int rc;
        !           284: 
        !           285:   rc = sqlite3_prepare_v2 (db,
        !           286:     "INSERT OR REPLACE INTO user_profile "
        !           287:     "(id, age_years, height_cm, weight_kg, activity_level, gender, calorie_target) "
        !           288:     "VALUES (1, ?1, ?2, ?3, ?4, ?5, ?6)",
        !           289:     -1, &stmt, NULL);
        !           290: 
        !           291:   if (rc != SQLITE_OK)
        !           292:     {
        !           293:       fprintf (stderr, _("gnutrition: SQL error: %s\n"), sqlite3_errmsg (db));
        !           294:       return -1;
        !           295:     }
        !           296: 
        !           297:   sqlite3_bind_int (stmt, 1, profile->age_years);
        !           298:   sqlite3_bind_double (stmt, 2, profile->height_cm);
        !           299:   sqlite3_bind_double (stmt, 3, profile->weight_kg);
        !           300:   sqlite3_bind_int (stmt, 4, profile->activity_level);
        !           301:   sqlite3_bind_int (stmt, 5, profile->gender);
        !           302:   sqlite3_bind_int (stmt, 6, profile->calorie_target);
        !           303: 
        !           304:   rc = sqlite3_step (stmt);
        !           305:   sqlite3_finalize (stmt);
        !           306: 
        !           307:   if (rc != SQLITE_DONE)
        !           308:     {
        !           309:       fprintf (stderr, _("gnutrition: cannot save profile: %s\n"),
        !           310:                sqlite3_errmsg (db));
        !           311:       return -1;
        !           312:     }
        !           313: 
        !           314:   return 0;
        !           315: }
        !           316: 
        !           317: int
        !           318: log_get_profile (sqlite3 *db, struct user_profile *profile)
        !           319: {
        !           320:   sqlite3_stmt *stmt;
        !           321:   int rc;
        !           322: 
        !           323:   memset (profile, 0, sizeof *profile);
        !           324: 
        !           325:   rc = sqlite3_prepare_v2 (db,
        !           326:     "SELECT age_years, height_cm, weight_kg, "
        !           327:     "activity_level, gender, calorie_target "
        !           328:     "FROM user_profile WHERE id = 1",
        !           329:     -1, &stmt, NULL);
        !           330: 
        !           331:   if (rc != SQLITE_OK)
        !           332:     {
        !           333:       fprintf (stderr, _("gnutrition: SQL error: %s\n"), sqlite3_errmsg (db));
        !           334:       return -1;
        !           335:     }
        !           336: 
        !           337:   rc = sqlite3_step (stmt);
        !           338: 
        !           339:   if (rc == SQLITE_ROW)
        !           340:     {
        !           341:       profile->age_years = sqlite3_column_int (stmt, 0);
        !           342:       profile->height_cm = sqlite3_column_double (stmt, 1);
        !           343:       profile->weight_kg = sqlite3_column_double (stmt, 2);
        !           344:       profile->activity_level = sqlite3_column_int (stmt, 3);
        !           345:       profile->gender = sqlite3_column_int (stmt, 4);
        !           346:       profile->calorie_target = sqlite3_column_int (stmt, 5);
        !           347:       sqlite3_finalize (stmt);
        !           348:       return 0;
        !           349:     }
        !           350:   else if (rc == SQLITE_DONE)
        !           351:     {
        !           352:       sqlite3_finalize (stmt);
        !           353:       return 1;  /* No profile saved yet.  */
        !           354:     }
        !           355:   else
        !           356:     {
        !           357:       fprintf (stderr, _("gnutrition: SQL error: %s\n"), sqlite3_errmsg (db));
        !           358:       sqlite3_finalize (stmt);
        !           359:       return -1;
        !           360:     }
        !           361: }
        !           362: 
        !           363: void
        !           364: log_list_free (struct log_list *list)
        !           365: {
        !           366:   size_t i;
        !           367:   if (!list)
        !           368:     return;
        !           369:   for (i = 0; i < list->count; i++)
        !           370:     {
        !           371:       free (list->items[i].description);
        !           372:       free (list->items[i].date);
        !           373:     }
        !           374:   free (list->items);
        !           375:   list->items = NULL;
        !           376:   list->count = 0;
        !           377:   list->capacity = 0;
        !           378: }
        !           379: 
        !           380: int
        !           381: log_get_dates (sqlite3 *db, struct date_list *results)
        !           382: {
        !           383:   sqlite3_stmt *stmt;
        !           384:   int rc;
        !           385: 
        !           386:   results->dates = NULL;
        !           387:   results->count = 0;
        !           388:   results->capacity = 0;
        !           389: 
        !           390:   rc = sqlite3_prepare_v2 (db,
        !           391:     "SELECT DISTINCT date FROM food_log ORDER BY date",
        !           392:     -1, &stmt, NULL);
        !           393: 
        !           394:   if (rc != SQLITE_OK)
        !           395:     {
        !           396:       fprintf (stderr, _("gnutrition: SQL error: %s\n"), sqlite3_errmsg (db));
        !           397:       return -1;
        !           398:     }
        !           399: 
        !           400:   while ((rc = sqlite3_step (stmt)) == SQLITE_ROW)
        !           401:     {
        !           402:       const char *d = (const char *) sqlite3_column_text (stmt, 0);
        !           403:       if (results->count >= results->capacity)
        !           404:         {
        !           405:           size_t new_cap;
        !           406:           char **tmp;
        !           407: 
        !           408:           new_cap = results->capacity == 0
        !           409:                     ? INITIAL_CAPACITY : results->capacity * 2;
        !           410:           tmp = realloc (results->dates, new_cap * sizeof *tmp);
        !           411:           if (!tmp)
        !           412:             {
        !           413:               fprintf (stderr, _("gnutrition: memory exhausted\n"));
        !           414:               sqlite3_finalize (stmt);
        !           415:               date_list_free (results);
        !           416:               return -1;
        !           417:             }
        !           418:           results->dates = tmp;
        !           419:           results->capacity = new_cap;
        !           420:         }
        !           421:       results->dates[results->count] = strdup (d ? d : "");
        !           422:       if (!results->dates[results->count])
        !           423:         {
        !           424:           fprintf (stderr, _("gnutrition: memory exhausted\n"));
        !           425:           sqlite3_finalize (stmt);
        !           426:           date_list_free (results);
        !           427:           return -1;
        !           428:         }
        !           429:       results->count++;
        !           430:     }
        !           431: 
        !           432:   sqlite3_finalize (stmt);
        !           433: 
        !           434:   if (rc != SQLITE_DONE)
        !           435:     {
        !           436:       fprintf (stderr, _("gnutrition: SQL error: %s\n"), sqlite3_errmsg (db));
        !           437:       date_list_free (results);
        !           438:       return -1;
        !           439:     }
        !           440: 
        !           441:   return 0;
        !           442: }
        !           443: 
        !           444: void
        !           445: date_list_free (struct date_list *list)
        !           446: {
        !           447:   size_t i;
        !           448:   if (!list)
        !           449:     return;
        !           450:   for (i = 0; i < list->count; i++)
        !           451:     free (list->dates[i]);
        !           452:   free (list->dates);
        !           453:   list->dates = NULL;
        !           454:   list->count = 0;
        !           455:   list->capacity = 0;
        !           456: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>