/* 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 Chrysalide. If not, see .
*/
#include "collection.h"
#include
#include
#include
#include
#include
#include "collection-int.h"
#include "misc/rlestr.h"
#include "../../common/extstr.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)
{
if (collec->items != NULL)
{
g_list_free_full(collec->items, g_object_unref);
collec->items = NULL;
}
if (collec->sorted != NULL)
{
g_list_free_full(collec->sorted, g_object_unref);
collec->sorted = NULL;
}
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)
{
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 = collection générique d'éléments à consulter. *
* *
* Description : Décrit le type de collection manipulée. *
* *
* Retour : Description humaine de la collection. *
* *
* Remarques : - *
* *
******************************************************************************/
const char *g_db_collection_get_name(const GDbCollection *collec)
{
return _(collec->name);
}
/******************************************************************************
* *
* Paramètres : collec = ensemble d'éléments à considérer. *
* pbuf = paquet de données où venir puiser les infos. *
* action = commande de la requête. [OUT] *
* dest = élément de collection ou NULL pour un rejet. [OUT] *
* *
* Description : Réceptionne un élément depuis une requête réseau. *
* *
* Retour : Bilan de l'exécution de l'opération. *
* *
* Remarques : - *
* *
******************************************************************************/
bool _g_db_collection_unpack(GDbCollection *collec, packed_buffer *pbuf, DBAction *action, GDbItem **dest)
{
bool result; /* Bilan à faire remonter */
uint32_t tmp32; /* Valeur sur 32 bits */
GDbItem *item; /* Définition d'élément visé */
result = extract_packed_buffer(pbuf, &tmp32, sizeof(uint32_t), true);
if (!result) goto qck_exit;
*action = tmp32;
result = (*action >= 0 && *action < DBA_COUNT);
if (!result) goto qck_exit;
item = g_object_new(collec->type, NULL);
result = g_db_item_unpack(item, pbuf);
if (!result) goto exit;
if (dest != NULL)
{
g_object_ref(G_OBJECT(item));
*dest = item;
}
exit:
g_object_unref(G_OBJECT(item));
qck_exit:
return result;
}
/******************************************************************************
* *
* Paramètres : collec = ensemble d'éléments à considérer. *
* pbuf = paquet de données où venir puiser les infos. *
* 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_unpack(GDbCollection *collec, packed_buffer *pbuf, sqlite3 *db)
{
bool result; /* Bilan à faire remonter */
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 */
result = _g_db_collection_unpack(collec, pbuf, &action, &item);
if (!result) return 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 (result && db != NULL)
result = g_db_collection_store_item(collec, item, db);
/* En cas d'erreur, il faut retirer l'élément */
if (!result)
_g_db_collection_remove_item(collec, item, true, false);
}
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, true);
/* 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. *
* pbuf = paquet de données où venir inscrire les infos. *
* 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_pack(GDbCollection *collec, packed_buffer *pbuf, DBAction action, GDbItem *item)
{
bool result; /* Bilan à retourner */
result = extend_packed_buffer(pbuf, (uint32_t []) { DBC_COLLECTION }, sizeof(uint32_t), true);
if (result)
result = extend_packed_buffer(pbuf, (uint32_t []) { collec->featuring }, sizeof(uint32_t), true);
if (result)
result = extend_packed_buffer(pbuf, (uint32_t []) { action }, sizeof(uint32_t), true);
if (result)
result = g_db_item_pack(item, pbuf);
return result;
}
/******************************************************************************
* *
* Paramètres : collec = ensemble d'éléments à considérer. *
* pbuf = paquet de données où venir inscrire les infos. *
* *
* 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_pack_all_updates(GDbCollection *collec, packed_buffer *pbuf)
{
bool result; /* Bilan à renvoyer */
GList *iter; /* Boucle de parcours */
result = true;
/**
* La gestion des accès s'effectue depuis le seul appelant : la fonction
* g_cdb_archive_add_client().
*/
for (iter = g_list_first(collec->items);
iter != NULL && result;
iter = g_list_next(iter))
{
result = g_db_collection_pack(collec, pbuf, 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é. *
* signal = émet un événement pour signaler le retrait. *
* *
* 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 signal)
{
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->sorted, item, (GCompareFunc)g_db_item_compare_with_timestamp);
result = (found != NULL);
if (result)
{
internal = G_DB_ITEM(found->data);
collec->sorted = g_list_delete_link(collec->sorted, found);
found = g_list_find(collec->items, item);
assert(found != NULL);
collec->items = g_list_delete_link(collec->items, found);
if (signal)
g_signal_emit_by_name(collec, "content-changed", DBA_REM_ITEM, internal);
g_object_unref(G_OBJECT(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);
break;
case SQLITE_TEXT:
values[i].cstring = (const char *)sqlite3_column_text(stmt, i);
break;
case SQLITE_BLOB:
assert(0);
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);
g_object_unref(G_OBJECT(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. *
* pbuf = paquet de données où venir inscrire des infos. *
* *
* Description : Collecte les informations utiles pour un nouvel arrivant. *
* *
* Retour : Bilan du déroulement des opérations. *
* *
* Remarques : - *
* *
******************************************************************************/
bool pack_all_collection_updates(GList *list, packed_buffer *pbuf)
{
bool result; /* Bilan à retourner */
GList *iter; /* Boucle de parcours */
GDbCollection *collec; /* Collection visée manipulée */
result = true;
/**
* Cette procédure n'est appelée que depuis g_cdb_archive_process(),
* qui bloque son exécution jusqu'à la fin des opérations.
*
* On a donc l'assurance d'un récupérer tous les éléments d'un coup,
* sans activité parallèle.
*/
for (iter = g_list_first(list); iter != NULL && result; iter = g_list_next(iter))
{
collec = G_DB_COLLECTION(iter->data);
result = g_db_collection_pack_all_updates(collec, pbuf);
}
return result;
}
/******************************************************************************
* *
* Paramètres : list = ensemble de collectons à traiter. *
* pbuf = paquet de données où venir puiser les infos. *
* 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, packed_buffer *pbuf, 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*/
/**
* Cette procédure n'est appelée que depuis g_cdb_archive_process(),
* qui bloque son exécution jusqu'à la fin des opérations.
*
* On a donc l'assurance d'un traitement global homgène des horodatages.
*/
status = unpack_timestamp(×tamp, pbuf);
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;
}