/* Chrysalide - Outil d'analyse de fichiers binaires * collection.c - gestion d'éléments ajoutés par collection * * Copyright (C) 2014-2017 Cyrille Bagard * * This file is part of Chrysalide. * * Chrysalide is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * Chrysalide is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Foobar. If not, see . */ #include "collection.h" #include #include #include #include #include "collection-int.h" #include "misc/rlestr.h" #include "../../common/extstr.h" #include "../../common/io.h" #include "../../glibext/chrysamarshal.h" /* Initialise la classe des collections génériques d'éléments. */ static void g_db_collection_class_init(GDbCollectionClass *); /* Initialise une collection générique d'éléments. */ static void g_db_collection_init(GDbCollection *); /* Supprime toutes les références externes. */ static void g_db_collection_dispose(GDbCollection *); /* Procède à la libération totale de la mémoire. */ static void g_db_collection_finalize(GDbCollection *); /* --------------------- MANIPULATIONS AVEC UNE BASE DE DONNEES --------------------- */ /* Décrit les colonnes utiles à un chargement de données. */ static bool _g_db_collection_setup_load(GDbCollection *, bound_value **, size_t *); /* Décrit les colonnes utiles à un chargement de données. */ static bool g_db_collection_setup_load(GDbCollection *, bound_value **, size_t *); /* Enregistre un élément de collection dans une base de données. */ static bool g_db_collection_store_item(const GDbCollection *, const GDbItem *, sqlite3 *); /* Met à jour un élément de collection dans une base de données. */ static bool g_db_collection_store_updated_item(const GDbCollection *, const GDbItem *, sqlite3 *); /* Indique le type défini pour une collection générique d'éléments. */ G_DEFINE_TYPE(GDbCollection, g_db_collection, G_TYPE_OBJECT); /****************************************************************************** * * * Paramètres : klass = classe à initialiser. * * * * Description : Initialise la classe des collections génériques d'éléments. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_db_collection_class_init(GDbCollectionClass *klass) { GObjectClass *object; /* Autre version de la classe */ object = G_OBJECT_CLASS(klass); object->dispose = (GObjectFinalizeFunc/* ! */)g_db_collection_dispose; object->finalize = (GObjectFinalizeFunc)g_db_collection_finalize; klass->setup_load = (collec_setup_load_fc)_g_db_collection_setup_load; g_signal_new("content-changed", G_TYPE_DB_COLLECTION, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(GDbCollectionClass, content_changed), NULL, NULL, g_cclosure_user_marshal_VOID__ENUM_OBJECT, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_OBJECT); } /****************************************************************************** * * * Paramètres : collec = instance à initialiser. * * * * Description : Initialise une collection générique d'éléments. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_db_collection_init(GDbCollection *collec) { collec->binary = NULL; g_rw_lock_init(&collec->params_access); } /****************************************************************************** * * * Paramètres : collec = instance d'objet GLib à traiter. * * * * Description : Supprime toutes les références externes. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_db_collection_dispose(GDbCollection *collec) { G_OBJECT_CLASS(g_db_collection_parent_class)->dispose(G_OBJECT(collec)); } /****************************************************************************** * * * Paramètres : collec = instance d'objet GLib à traiter. * * * * Description : Procède à la libération totale de la mémoire. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_db_collection_finalize(GDbCollection *collec) { g_rw_lock_clear(&collec->params_access); G_OBJECT_CLASS(g_db_collection_parent_class)->finalize(G_OBJECT(collec)); } /****************************************************************************** * * * Paramètres : id = identifiant réseau des éléments à traiter. * * type = type GLib des éléments à placer dans la collection. * * name = indique le nom désignant la table associée. * * * * Description : Prépare la mise en place d'une nouvelle collection. * * * * Retour : Adresse de l'instance ou NULL en cas d'échec. * * * * Remarques : - * * * ******************************************************************************/ GDbCollection *g_db_collection_new(uint32_t id, GType type, const char *name) { GDbCollection *result; /* Adresse à retourner */ result = g_object_new(G_TYPE_DB_COLLECTION, NULL); result->featuring = id; result->type = type; result->name = name; return result; } /****************************************************************************** * * * Paramètres : collec = collection générique d'éléments à compléter. * * binary = binaire sur lequel appliquer les éléments. * * * * Description : Attache à une collection un binaire pour les éléments listés.* * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void g_db_collection_link_to_binary(GDbCollection *collec, GLoadedBinary *binary) { g_object_ref(G_OBJECT(binary)); collec->binary = binary; } /****************************************************************************** * * * Paramètres : collec = collection générique d'éléments à consulter. * * * * Description : Décrit le type des éléments rassemblées dans une collection. * * * * Retour : Identifiant interne des éléments collectionés. * * * * Remarques : - * * * ******************************************************************************/ uint32_t g_db_collection_get_feature(const GDbCollection *collec) { return collec->featuring; } /****************************************************************************** * * * Paramètres : collec = ensemble d'éléments à considérer. * * fd = flux ouvert en lecture pour la réception de données.* * db = base de données à mettre à jour. * * * * Description : Réceptionne et traite une requête réseau pour collection. * * * * Retour : Bilan de l'exécution de l'opération. * * * * Remarques : Cette fonction est uniquement destinée aux appels depuis * * la fonction g_cdb_archive_process() ; une partie des * * informations ont déjà été tirées des échanges protocolaires. * * * ******************************************************************************/ bool g_db_collection_recv(GDbCollection *collec, int fd, sqlite3 *db) { bool result; /* Bilan à faire remonter */ uint32_t val32; /* Valeur sur 32 bits */ bool status; /* Bilan de lecture initiale */ DBAction action; /* Commande de la requête */ GDbItem *item; /* Définition d'élément visé */ GList *found; /* Test de présence existante */ timestamp_t inactive; /* Horodatage de désactivation */ status = safe_recv(fd, &val32, sizeof(uint32_t), 0); if (!status) return false; action = be32toh(val32); if (action < 0 || action >= DBA_COUNT) return false; item = g_object_new(collec->type, NULL); status = g_db_item_recv(item, fd, 0); if (!status) return false; result = false; switch (action) { case DBA_ADD_ITEM: /* Ecrasement des horodatages par les valeurs communes du serveur */ if (db != NULL) g_db_item_set_server_side(item); result = g_db_collection_add_item(collec, item); if (result) { if (collec->binary != NULL && g_db_item_is_active(item)) result = g_db_item_apply(item, collec->binary); if (db != NULL) result &= g_db_collection_store_item(collec, item, db); } if (!result) /* TODO : retirer l'élément */; break; case DBA_REM_ITEM: g_db_collection_wlock(collec); found = g_list_find_custom(collec->items, item, (GCompareFunc)g_db_item_compare_with_timestamp); result = (found != NULL); if (result) { /* Côté client */ if (db == NULL) result = _g_db_collection_remove_item(collec, item, false); /* Côté serveur */ else { if (g_db_item_is_active(G_DB_ITEM(found->data))) { inactive = _g_db_collection_compute_inactivity_timestamp(collec, false); result = _g_db_collection_update_item_activity(collec, item, &inactive, false); } } } g_db_collection_wunlock(collec); break; case DBA_CHANGE_STATE: if (db == NULL) { if (g_db_item_is_active(item)) result = g_db_collection_update_item_activity(collec, item, NULL); else { inactive = g_db_item_get_timestamp(item) + 1; result = g_db_collection_update_item_activity(collec, item, &inactive); } } else result = false; break; default: /* Pour GCC : DBA_COUNT */ break; } g_object_unref(G_OBJECT(item)); return result; } /****************************************************************************** * * * Paramètres : collec = ensemble d'éléments à considérer. * * fd = flux ouvert en écriture pour l'émission de données. * * action = avenir de l'élément fourni. * * item = élément de collection à sérialiser. * * * * Description : Envoie pour traitement une requête réseau pour collection. * * * * Retour : Bilan de l'exécution de l'opération. * * * * Remarques : - * * * ******************************************************************************/ bool g_db_collection_send(GDbCollection *collec, int fd, DBAction action, GDbItem *item) { bool status; /* Bilan de lecture initiale */ status = safe_send(fd, (uint32_t []) { htobe32(DBC_COLLECTION) }, sizeof(uint32_t), MSG_MORE); if (!status) return false; status = safe_send(fd, (uint32_t []) { htobe32(collec->featuring) }, sizeof(uint32_t), MSG_MORE); if (!status) return false; status = safe_send(fd, (uint32_t []) { htobe32(action) }, sizeof(uint32_t), MSG_MORE); if (!status) return false; status = g_db_item_send(item, fd, 0); if (!status) return false; return true; } /****************************************************************************** * * * Paramètres : collec = ensemble d'éléments à considérer. * * fd = flux ouvert en écriture pour l'émission de données. * * * * Description : Envoie pour mise à jour tous les éléments courants. * * * * Retour : Bilan de l'exécution de l'opération. * * * * Remarques : - * * * ******************************************************************************/ bool g_db_collection_send_all_updates(GDbCollection *collec, int fd) { bool result; /* Bilan à renvoyer */ GList *iter; /* Boucle de parcours */ result = true; /* TODO : lock ? */ for (iter = g_list_first(collec->items); iter != NULL && result; iter = g_list_next(iter)) { result = g_db_collection_send(collec, fd, DBA_ADD_ITEM, G_DB_ITEM(iter->data)); } return result; } /****************************************************************************** * * * Paramètres : collec = collection à mettre à jour. * * write = précise le type d'accès prévu (lecture/écriture). * * lock = indique le sens du verrouillage à mener. * * * * Description : Met à disposition un encadrement des accès aux éléments. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void g_db_collection_lock_unlock(GDbCollection *collec, bool write, bool lock) { if (write) { if (lock) g_rw_lock_writer_lock(&collec->params_access); else g_rw_lock_writer_unlock(&collec->params_access); } else { if (lock) g_rw_lock_reader_lock(&collec->params_access); else g_rw_lock_reader_unlock(&collec->params_access); } } /****************************************************************************** * * * Paramètres : collec = ensemble d'éléments à consulter. * * * * Description : Renvoie la liste des éléments rassemblés. * * * * Retour : Liste d'éléments à parcourir. * * * * Remarques : - * * * ******************************************************************************/ GList *g_db_collection_list_items(const GDbCollection *collec) { /** * Un verrou doit être posé ! * Il n'y a pas d'assert() possible pour le vérifier... */ return collec->items; } /****************************************************************************** * * * Paramètres : collec = ensemble d'éléments à consulter. * * ... = clef identifiant de manière unique un élément. * * * * Description : Détermine si un élément est déjà présent ou non. * * * * Retour : Elément trouvé ou NULL si aucun. * * * * Remarques : - * * * ******************************************************************************/ GDbItem *g_db_collection_has_key(GDbCollection *collec, ...) { GDbItem *result; /* Bilan à retourner */ va_list ap; /* Liste d'arguments en plus */ va_start(ap, collec); result = G_DB_COLLECTION_GET_CLASS(collec)->has_key(collec, ap); va_end(ap); return result; } /****************************************************************************** * * * Paramètres : collec = ensemble d'éléments à consulter. * * item = élément complet dont un double est à rechercher. * * * * Description : Détermine si un élément est déjà présent ou non. * * * * Retour : true si un élément similaire est présent dans la collection. * * * * Remarques : - * * * ******************************************************************************/ bool g_db_collection_has_item(GDbCollection *collec, GDbItem *item) { bool result; /* Bilan à retourner */ GList *found; /* Test de présence existante */ /** * Un verrou doit être posé ! * Il n'y a pas d'assert() possible pour le vérifier... */ found = g_list_find_custom(collec->items, item, (GCompareFunc)g_db_item_compare_with_timestamp); result = (found != NULL); return result; } /****************************************************************************** * * * Paramètres : collec = ensemble d'éléments à considérer. * * item = élément de collection à manipuler. * * lock = indique si le verrou d'écriture doit être posé. * * * * Description : Procède à l'ajout d'un nouvel élément dans la collection. * * * * Retour : Bilan de l'exécution de l'opération. * * * * Remarques : L'appelant reste le propriétaire de l'object transféré. * * * ******************************************************************************/ bool _g_db_collection_add_item(GDbCollection *collec, GDbItem *item, bool lock) { bool result; /* Bilan à faire remonter */ result = true; if (lock) g_db_collection_wlock(collec); g_object_ref(G_OBJECT(item)); collec->items = g_list_append(collec->items, item); g_object_ref(G_OBJECT(item)); collec->sorted = g_list_insert_sorted(collec->sorted, item, (GCompareFunc)g_db_item_compare_with_timestamp); g_signal_emit_by_name(collec, "content-changed", DBA_ADD_ITEM, item); if (lock) g_db_collection_wunlock(collec); return result; } /****************************************************************************** * * * Paramètres : collec = ensemble d'éléments à considérer. * * item = élément de collection à manipuler. * * lock = indique si le verrou d'écriture doit être posé. * * * * Description : Procède au retrait d'un nouvel élément dans la collection. * * * * Retour : Bilan de l'exécution de l'opération. * * * * Remarques : L'appelant reste le propriétaire de l'object transféré. * * * ******************************************************************************/ bool _g_db_collection_remove_item(GDbCollection *collec, GDbItem *item, bool lock) { bool result; /* Bilan à faire remonter */ GList *found; /* Test de présence existante */ GDbItem *internal; /* Elément interne à modifier */ result = true; if (lock) g_db_collection_wlock(collec); found = g_list_find_custom(collec->items, item, (GCompareFunc)g_db_item_compare_with_timestamp); result = (found != NULL); if (result) { internal = G_DB_ITEM(found->data); collec->items = g_list_delete_link(collec->items, found); g_signal_emit_by_name(collec, "content-changed", DBA_REM_ITEM, internal); g_object_unref(G_OBJECT(internal)); } if (lock) g_db_collection_wunlock(collec); return result; } /****************************************************************************** * * * Paramètres : collec = ensemble d'éléments à considérer. * * lock = indique si le verrou d'écriture doit être posé. * * * * Description : Détermine l'horodatage le plus jeune pour une désactivation. * * * * Retour : Horodatage à utiliser pour une phase de désactivation. * * * * Remarques : - * * * ******************************************************************************/ timestamp_t _g_db_collection_compute_inactivity_timestamp(GDbCollection *collec, bool lock) { timestamp_t result; /* Horodatage à retourner */ GList *iter; /* Boucle de parcours */ GDbItem *item; /* Elément interne à consulter */ timestamp_t stamp; /* Horodatage de l'élément */ result = TIMESTAMP_ALL_ACTIVE; if (lock) g_db_collection_wlock(collec); for (iter = g_list_first(collec->items); iter != NULL; iter = g_list_next(iter)) { item = G_DB_ITEM(iter->data); if (!g_db_item_is_active(item)) { stamp = g_db_item_get_timestamp(item); if (timestamp_is_younger(stamp, result)) result = stamp; } } if (lock) g_db_collection_wunlock(collec); return result; } /****************************************************************************** * * * Paramètres : collec = ensemble d'éléments à considérer. * * item = élément de collection à manipuler. * * lock = indique si le verrou d'écriture doit être posé. * * * * Description : Met à jour le statut d'activité d'un élément de collection. * * * * Retour : Bilan de l'exécution de l'opération. * * * * Remarques : L'appelant reste le propriétaire de l'object transféré. * * * ******************************************************************************/ bool _g_db_collection_update_item_activity(GDbCollection *collec, GDbItem *item, timestamp_t *timestamp, bool lock) { bool result; /* Bilan à faire remonter */ GList *found; /* Test de présence existante */ GDbItem *internal; /* Elément interne à modifier */ if (lock) g_db_collection_wlock(collec); found = g_list_find_custom(collec->items, item, (GCompareFunc)g_db_item_compare_without_timestamp); result = (found != NULL); if (result) { internal = G_DB_ITEM(found->data); result = g_db_item_set_activity(internal, collec->binary, timestamp); g_signal_emit_by_name(collec, "content-changed", DBA_CHANGE_STATE, internal); } if (lock) g_db_collection_wunlock(collec); return result; } /****************************************************************************** * * * Paramètres : collec = ensemble d'éléments à considérer. * * timestamp = date du dernier élément à garder comme actif. * * inactive = date du premier élément inactif rencontré. [OUT] * * db = base de données à mettre à jour. * * * * Description : Active les éléments en amont d'un horodatage donné. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ GList *g_db_collection_set_last_active(GDbCollection *collec, timestamp_t timestamp, timestamp_t *inactive, sqlite3 *db) { GList *result; /* Liste d'inactifs à renvoyer */ GList *i; /* Parcours des éléments */ GDbItem *item; /* Elément à traiter */ timestamp_t stamp; /* Horodatage de l'élément */ result = NULL; for (i = g_list_first(collec->items); i != NULL; i = g_list_next(i)) { item = G_DB_ITEM(i->data); stamp = g_db_item_get_timestamp(item); if (timestamp_is_younger(stamp, timestamp)) { if (!g_db_item_is_active(item)) { /* ... */g_db_item_set_activity(item, collec->binary, NULL); /* ... */g_db_collection_store_updated_item(collec, item, db); g_signal_emit_by_name(collec, "content-changed", DBA_CHANGE_STATE, item); } } else { if (!g_db_item_is_active(item)) { if (timestamp_is_younger(stamp, *inactive)) *inactive = stamp; } else result = g_list_append(result, item); } } return result; } /****************************************************************************** * * * Paramètres : collec = ensemble d'éléments à considérer. * * item = élément à désactiver. * * timestamp = date du premier élément inactif rencontré. [OUT] * * * * Description : Désactive les éléments en aval d'un horodatage donné. * * * * Retour : true si l'élément a été traité, false sinon. * * * * Remarques : - * * * ******************************************************************************/ bool g_db_collection_set_inactive(GDbCollection *collec, GDbItem *item, timestamp_t *timestamp) { bool result; /* Bilan à retourner */ /* Si cette collection n'est pas concernée, on ne bouge pas ! */ if (G_OBJECT_TYPE(G_OBJECT(item)) != collec->type) return false; result = g_db_item_set_activity(item, collec->binary, timestamp); g_signal_emit_by_name(collec, "content-changed", DBA_CHANGE_STATE, item); return result; } /* ---------------------------------------------------------------------------------- */ /* MANIPULATIONS AVEC UNE BASE DE DONNEES */ /* ---------------------------------------------------------------------------------- */ /****************************************************************************** * * * Paramètres : collec = ensemble d'éléments spectateur des opérations. * * db = accès à la base de données. * * * * Description : Crée la table d'élément dans une base de données. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ bool g_db_collection_create_db_table(const GDbCollection *collec, sqlite3 *db) { return G_DB_COLLECTION_GET_CLASS(collec)->create_table(collec, db); } /****************************************************************************** * * * Paramètres : collec = ensemble d'éléments à consulter. * * values = tableau d'éléments à compléter. [OUT] * * count = nombre de descriptions renseignées. [OUT] * * * * Description : Décrit les colonnes utiles à un chargement de données. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ static bool _g_db_collection_setup_load(GDbCollection *collec, bound_value **values, size_t *count) { if (!setup_load_of_timestamp(NULL, "created", values, count)) return false; if (!setup_load_of_timestamp(NULL, "timestamp", values, count)) return false; if (!setup_load_of_rle_string(NULL, "author", values, count)) return false; if (!setup_load_of_rle_string(NULL, "tool", values, count)) return false; return true; } /****************************************************************************** * * * Paramètres : collec = ensemble d'éléments à consulter. * * values = tableau d'éléments à compléter. [OUT] * * count = nombre de descriptions renseignées. [OUT] * * * * Description : Décrit les colonnes utiles à un chargement de données. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ static bool g_db_collection_setup_load(GDbCollection *collec, bound_value **values, size_t *count) { *values = NULL; *count = 0; return G_DB_COLLECTION_GET_CLASS(collec)->setup_load(collec, values, count); } /****************************************************************************** * * * Paramètres : collec = ensemble d'éléments à peupler. * * db = base de données repondant aux requêtes. * * * * Description : Charge un ensemble d'éléments à partir d'une base de données.* * * * Retour : Bilan de l'exécution de l'opération. * * * * Remarques : - * * * ******************************************************************************/ bool g_db_collection_load_all_items(GDbCollection *collec, sqlite3 *db) { bool result; /* Conclusion à faire remonter */ bound_value *values; /* Champs de table à inclure */ size_t count; /* Nombre de ces champs */ char *sql; /* Requête SQL à construire */ size_t i; /* Boucle de parcours */ sqlite3_stmt *stmt; /* Déclaration mise en place */ int ret; /* Bilan d'un appel à SQLite */ int native_type; /* Type de valeur dans la base */ GDbItem *new; /* Nouvel élément à insérer */ result = false; if (!g_db_collection_setup_load(collec, &values, &count)) goto gdclai_building_values; /* Préparation de la requête */ sql = strdup("SELECT "); for (i = 0; i < count; i++) { if (i > 0) sql = stradd(sql, ", "); sql = stradd(sql, values[i].name); } sql = stradd(sql, " FROM "); sql = stradd(sql, collec->name); sql = stradd(sql, ";"); ret = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); if (ret != SQLITE_OK) { fprintf(stderr, "Can't prepare SELECT statment '%s' (ret=%d): %s\n", sql, ret, sqlite3_errmsg(db)); goto gdclai_exit; } /* Chargement des valeurs existantes */ result = true; for (ret = sqlite3_step(stmt); ret == SQLITE_ROW && result; ret = sqlite3_step(stmt)) { /* Conversion des valeurs */ for (i = 0; i < count; i++) { native_type = sqlite3_column_type(stmt, i); /** * On réalise une petite conversion selon le champ. * * Le filtre SQLITE_NATIVE est destiné à conserver le type choisi par * SQLite. Typiquement, une chaîne peut être à SQLITE_NULL ou SQLITE_TEXT * selon la valeur conservée dans la base. * * D'autres éléments, comme les localisations en mémoire, peuvent aussi * avoir un champ éventuellement nul, donc la définition à partir des * indications de la base de données reste importante. * * En ce qui concerne les valeurs numériques, SQLite ne fait pas de * distinction : tout passe par la fonction sqlite3VdbeIntValue(), * qui effectue des transtypages au besoin pour tout ce qui n'est * pas numérique. * * Pour les types internes SQLITE_INTEGER et SQLITE_BOOLEAN, * il est donc nécessaire d'ajuster en interne. */ if (native_type == SQLITE_INTEGER) native_type = SQLITE_INT64; if (values[i].type == SQLITE_NATIVE) values[i].type = native_type; else assert(values[i].type == native_type || values[i].type == SQLITE_INTEGER || values[i].type == SQLITE_BOOLEAN); switch (values[i].type) { case SQLITE_BOOLEAN: values[i].boolean = (bool)sqlite3_column_int(stmt, i); break; case SQLITE_INTEGER: values[i].integer = sqlite3_column_int(stmt, i); break; case SQLITE_INT64: values[i].integer64 = sqlite3_column_int64(stmt, i); break; case SQLITE_FLOAT: assert(0); /* TODO */ break; case SQLITE_TEXT: values[i].cstring = (const char *)sqlite3_column_text(stmt, i); break; case SQLITE_BLOB: assert(0); /* TODO */ break; case SQLITE_NULL: break; default: assert(0); break; } } /* Chargement d'un nouvel élément */ new = g_object_new(G_DB_COLLECTION(collec)->type, NULL); result = g_db_item_load(new, values, count); result &= g_db_collection_add_item(G_DB_COLLECTION(collec), new); } /* Sortie propre */ sqlite3_finalize(stmt); gdclai_exit: free(sql); gdclai_building_values: free_all_bound_values(values, count); return result; } /****************************************************************************** * * * Paramètres : collec = ensemble d'éléments à considérer. * * item = élément de collection à enregistrer. * * db = base de données à mettre à jour. * * * * Description : Enregistre un élément de collection dans une base de données.* * * * Retour : Bilan de l'exécution de l'opération. * * * * Remarques : - * * * ******************************************************************************/ static bool g_db_collection_store_item(const GDbCollection *collec, const GDbItem *item, sqlite3 *db) { bool result; /* Conclusion à faire remonter */ bound_value *values; /* Champs de table à inclure */ size_t count; /* Nombre de ces champs */ char *sql; /* Requête SQL à construire */ size_t i; /* Boucle de parcours */ sqlite3_stmt *stmt; /* Déclaration mise en place */ int ret; /* Bilan d'un appel à SQLite */ int index; /* Indice de valeur attachée */ result = false; if (!g_db_item_prepare_db_statement(item, &values, &count)) goto gdcsi_building_values; /* Préparation de la requête */ sql = strdup("INSERT INTO "); sql = stradd(sql, collec->name); sql = stradd(sql, " ("); for (i = 0; i < count; i++) { if (i > 0) sql = stradd(sql, ", "); sql = stradd(sql, values[i].name); } sql = stradd(sql, ") VALUES ("); for (i = 0; i < count; i++) { if (i > 0) sql = stradd(sql, ", "); if (values[i].type == SQLITE_RAW) sql = stradd(sql, values[i].cstring); else sql = stradd(sql, "?"); } sql = stradd(sql, ");"); ret = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); if (ret != SQLITE_OK) { fprintf(stderr, "Can't prepare INSERT statment '%s' (ret=%d): %s\n", sql, ret, sqlite3_errmsg(db)); goto gdcsi_exit; } /* Attribution des valeurs */ index = 1; for (i = 0; i < count; i++) { switch (values[i].type) { case SQLITE_BOOLEAN: ret = sqlite3_bind_int(stmt, index, values[i].boolean); index++; break; case SQLITE_INTEGER: ret = sqlite3_bind_int(stmt, index, values[i].integer); index++; break; case SQLITE_INT64: ret = sqlite3_bind_int64(stmt, index, values[i].integer64); index++; break; case SQLITE_TEXT: ret = sqlite3_bind_text(stmt, index, values[i].string, -1, values[i].delete); index++; break; case SQLITE_NULL: ret = sqlite3_bind_null(stmt, index); index++; break; default: assert(false); ret = SQLITE_ERROR; break; } if (ret != SQLITE_OK) { fprintf(stderr, "Can't bind value for parameter nb %d in '%s' (ret=%d): %s\n", index - 1, sql, ret, sqlite3_errmsg(db)); goto gdcsi_exit; } } /* Exécution finale */ ret = sqlite3_step(stmt); if (ret != SQLITE_DONE) { fprintf(stderr, "INSERT statement '%s' didn't return DONE (ret=%d): %s\n", sql, ret, sqlite3_errmsg(db)); goto gdcsi_exit; } sqlite3_finalize(stmt); result = true; gdcsi_exit: free(sql); gdcsi_building_values: free_all_bound_values(values, count); return result; } /****************************************************************************** * * * Paramètres : collec = ensemble d'éléments à considérer. * * item = élément de collection à enregistrer. * * db = base de données à mettre à jour. * * * * Description : Met à jour un élément de collection dans une base de données.* * * * Retour : Bilan de l'exécution de l'opération. * * * * Remarques : - * * * ******************************************************************************/ static bool g_db_collection_store_updated_item(const GDbCollection *collec, const GDbItem *item, sqlite3 *db) { bool result; /* Conclusion à faire remonter */ bound_value *values; /* Champs de table à inclure */ size_t count; /* Nombre de ces champs */ char *sql; /* Requête SQL à construire */ bool first; /* Première valeur ? */ size_t i; /* Boucle de parcours */ sqlite3_stmt *stmt; /* Déclaration mise en place */ int ret; /* Bilan d'un appel à SQLite */ int index; /* Indice de valeur attachée */ const bound_value *timestamp; /* Valeur de l'horodatage */ result = false; if (!g_db_item_prepare_db_statement(item, &values, &count)) goto gdcsui_building_values; /* Préparation de la requête */ sql = strdup("UPDATE "); sql = stradd(sql, collec->name); sql = stradd(sql, " SET timestamp = ? "); sql = stradd(sql, "WHERE "); first = true; for (i = 0; i < count; i++) { if (strcmp(values[i].name, "timestamp") == 0) continue; if (first) first = false; else sql = stradd(sql, " AND "); sql = stradd(sql, values[i].name); if (values[i].type == SQLITE_NULL) sql = stradd(sql, " IS "); else sql = stradd(sql, " = "); if (values[i].type == SQLITE_RAW) sql = stradd(sql, values[i].cstring); else sql = stradd(sql, "?"); } sql = stradd(sql, ";"); ret = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); if (ret != SQLITE_OK) { fprintf(stderr, "Can't prepare UPDATE statment '%s' (ret=%d): %s\n", sql, ret, sqlite3_errmsg(db)); goto gdcsui_exit; } /* Attribution des valeurs */ index = 1; timestamp = find_bound_value(values, count, "timestamp"); assert(timestamp->type == SQLITE_INT64); ret = sqlite3_bind_int64(stmt, index, timestamp->integer64); index++; if (ret != SQLITE_OK) { fprintf(stderr, "Can't bind value for parameter nb %d in '%s' (ret=%d): %s\n", index - 1, sql, ret, sqlite3_errmsg(db)); goto gdcsui_exit; } for (i = 0; i < count; i++) { if (strcmp(values[i].name, "timestamp") == 0) continue; switch (values[i].type) { case SQLITE_BOOLEAN: ret = sqlite3_bind_int(stmt, index, values[i].boolean); index++; break; case SQLITE_INTEGER: ret = sqlite3_bind_int(stmt, index, values[i].integer); index++; break; case SQLITE_INT64: ret = sqlite3_bind_int64(stmt, index, values[i].integer64); index++; break; case SQLITE_TEXT: ret = sqlite3_bind_text(stmt, index, values[i].string, -1, values[i].delete); index++; break; case SQLITE_NULL: ret = sqlite3_bind_null(stmt, index); index++; break; default: assert(false); ret = SQLITE_ERROR; break; } if (ret != SQLITE_OK) { fprintf(stderr, "Can't bind value for parameter nb %d in '%s' (ret=%d): %s\n", index - 1, sql, ret, sqlite3_errmsg(db)); goto gdcsui_exit; } } /* Exécution finale */ ret = sqlite3_step(stmt); if (ret != SQLITE_DONE) { fprintf(stderr, "UPDATE statement '%s' didn't return DONE (ret=%d): %s\n", sql, ret, sqlite3_errmsg(db)); goto gdcsui_exit; } sqlite3_finalize(stmt); result = true; gdcsui_exit: free(sql); gdcsui_building_values: free_all_bound_values(values, count); return result; } /* ---------------------------------------------------------------------------------- */ /* CREATION DE L'ABSTRACTION POUR COLLECTIONS */ /* ---------------------------------------------------------------------------------- */ /****************************************************************************** * * * Paramètres : list = ensemble de collectons à parcourir. * * binary = binaire sur lequel appliquer les éléments. * * * * Description : Attache un binaire à une série de collections. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void attach_binary_to_collections(GList *list, GLoadedBinary *binary) { GList *iter; /* Boucle de parcours */ for (iter = g_list_first(list); iter != NULL; iter = g_list_next(iter)) { g_db_collection_link_to_binary(G_DB_COLLECTION(iter->data), binary); } } /****************************************************************************** * * * Paramètres : list = ensemble de collectons à parcourir. * * id = identifiant interne du type d'éléments groupés. * * * * Description : Recherche une collection correspondant à un type donné. * * * * Retour : Collection trouvée ou NULL en cas d'échec. * * * * Remarques : - * * * ******************************************************************************/ GDbCollection *find_collection_in_list(GList *list, uint32_t id) { GDbCollection *result; /* Collection trouvée renvoyée */ GList *iter; /* Boucle de parcours */ result = NULL; for (iter = g_list_first(list); iter != NULL; iter = g_list_next(iter)) { result = G_DB_COLLECTION(iter->data); if (g_db_collection_get_feature(result) == id) break; } return (iter != NULL ? result : NULL); } /****************************************************************************** * * * Paramètres : list = ensemble de collectons à parcourir. * * write = précise le type d'accès prévu (lecture/écriture). * * lock = indique le sens du verrouillage à mener. * * * * Description : Met à disposition un encadrement des accès aux éléments. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void lock_unlock_collections(GList *list, bool write, bool lock) { GList *iter; /* Boucle de parcours */ for (iter = g_list_first(list); iter != NULL; iter = g_list_next(iter)) { g_db_collection_lock_unlock(G_DB_COLLECTION(iter->data), write, lock); } } /****************************************************************************** * * * Paramètres : list = ensemble de collectons à traiter. * * fd = canal de communication ouvert en lecture. * * db = base de données à mettre à jour. * * * * Description : Met à jour les statuts d'activité des éléments. * * * * Retour : Bilan du déroulement des opérations. * * * * Remarques : - * * * ******************************************************************************/ bool update_activity_in_collections(GList *list, int fd, sqlite3 *db) { bool result; /* Résultat global à renvoyer */ bool status; /* Bilan de lecture initiale */ timestamp_t timestamp; /* Horodatage de limite */ timestamp_t inactive; /* Date du premier inactif */ GList *remaining; /* Eléments restants à traiter */ GList *c; /* Boucle de parcours #1 */ GDbCollection *collec; /* Collection à manipuler */ GList *got; /* Eléments restants à traiter */ GList *i; /* Boucle de parcours #2 */ GDbItem *item; /* Elément collecté à manipuler*/ /* TODO : lock ? */ status = recv_timestamp(×tamp, fd, 0); if (!status) return false; inactive = TIMESTAMP_ALL_ACTIVE; remaining = NULL; wlock_collections(list); for (c = g_list_first(list); c != NULL; c = g_list_next(c)) { collec = G_DB_COLLECTION(c->data); got = g_db_collection_set_last_active(collec, timestamp, &inactive, db); remaining = g_list_concat(remaining, got); } gint sort_with_timestamp(GDbItem *a, GDbItem *b) { return g_db_item_cmp(a, b, false); } remaining = g_list_sort(remaining, (GCompareFunc)sort_with_timestamp); result = true; for (i = g_list_last(remaining); i != NULL && result; i = g_list_previous(i)) { item = G_DB_ITEM(i->data); for (c = g_list_first(list); c != NULL && result; c = g_list_next(c)) { collec = G_DB_COLLECTION(c->data); if (g_db_collection_set_inactive(collec, item, &inactive)) { result = g_db_collection_store_updated_item(collec, item, db); break; } } } wunlock_collections(list); g_list_free(remaining); return result; }