/* Chrysalide - Outil d'analyse de fichiers binaires
* snapshots.c - gestion des instantanés de base de données
*
* Copyright (C) 2019-2020 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 "snapshots.h"
#include
#include
#include
#include
#include "../../gtkext/easygtk.h"
/* Colonnes de l'arborescence d'instantanés */
typedef enum _SnapshotTreeColumn
{
STC_ICON, /* Image de représentation */
STC_TITLE, /* Meilleure représentation */
STC_ID, /* Identifiant de l'instantané */
STC_TIMESTAMP, /* Valeur brute d'horodatage */
STC_NAME, /* Désignation de l'instantané */
STC_DESC, /* Description de l'instantané */
} SnapshotTreeColumn;
/* Réagit à une fermeture de la boîte de dialogue. */
static void on_dialog_destroy(GtkWidget *, GtkBuilder *);
/* Réagit à un changement dans le choix du serveur. */
static void on_server_selection_changed(GtkComboBox *, GtkBuilder *);
/* Recherche un élément d'arborescence selon un identifiant. */
static bool find_suitable_parent(GtkTreeModel *, GtkTreeIter *, const char *, GtkTreeIter *);
/* Met à jour l'affichage avec une nouvelle liste d'instantanés. */
static void on_snapshots_updated(GAnalystClient *, GtkBuilder *);
/* Réinitialise la zone d'affichage des informations. */
static void reset_information_area(GtkBuilder *);
/* Active ou non l'accès à la zone d'affichage des informations. */
static void update_information_area_access(GtkBuilder *, bool);
/* Active ou non l'accès à la zone de contrôle des instantanés. */
static void update_control_area_access(GtkBuilder *, bool, bool, bool);
/* Met à jour l'affichage des informations d'un instantané. */
static void on_tree_selection_changed(GtkTreeSelection *, GtkBuilder *);
/* Restaure sur demande un nouvel instantané. */
static void restore_old_snapshot(GtkToolButton *, GtkBuilder *);
/* Crée sur demande un nouvel instantané. */
static void create_new_snapshot(GtkToolButton *, GtkBuilder *);
/* Supprime sur demande un instantané. */
static void remove_old_snapshot(GtkToolButton *, GtkBuilder *);
/* Supprime sur demande un instantané et ses successeurs. */
static void delete_old_snapshot(GtkToolButton *, GtkBuilder *);
/* Applique à un instantané ses nouvelles informations. */
static void apply_new_snapshot_info(GtkButton *, GtkBuilder *);
/* Ferme la boîte de dialogue. */
static void close_dialog_box(GtkButton *, GtkBuilder *);
/******************************************************************************
* *
* Paramètres : binary = binaire chargé en mémoire à traiter. *
* parent = fenêtre principale de l'éditeur. *
* outb = constructeur à détruire après usage. [OUT] *
* *
* Description : Affiche un gestionnaire d'instantanés de base de données. *
* *
* Retour : Adresse de la fenêtre mise en place. *
* *
* Remarques : - *
* *
******************************************************************************/
GtkWidget *create_snapshots_dialog(GLoadedBinary *binary, GtkWindow *parent, GtkBuilder **outb)
{
GtkWidget *result; /* Fenêtre à renvoyer */
GtkBuilder *builder; /* Constructeur utilisé */
GFile *file; /* Accès à une image interne */
GIcon *icon; /* Image de représentation */
GtkComboBox *combo; /* Liste de serveurs */
builder = gtk_builder_new_from_resource("/org/chrysalide/gui/dialogs/snapshots.ui");
*outb = builder;
result = GTK_WIDGET(gtk_builder_get_object(builder, "window"));
gtk_window_set_transient_for(GTK_WINDOW(result), parent);
g_object_ref(G_OBJECT(result));
g_object_set_data_full(G_OBJECT(builder), "window", result, g_object_unref);
g_object_ref(G_OBJECT(binary));
g_object_set_data_full(G_OBJECT(builder), "binary", binary, g_object_unref);
file = g_file_new_for_uri("resource:///org/chrysalide/gui/core/images/snapshot.png");
icon = g_file_icon_new(file);
g_object_set_data_full(G_OBJECT(builder), "icon", icon, g_object_unref);
file = g_file_new_for_uri("resource:///org/chrysalide/gui/core/images/snapshot_current.png");
icon = g_file_icon_new(file);
g_object_set_data_full(G_OBJECT(builder), "current_icon", icon, g_object_unref);
/* Connexion des signaux */
gtk_builder_add_callback_symbols(builder,
BUILDER_CALLBACK(on_dialog_destroy),
BUILDER_CALLBACK(on_server_selection_changed),
BUILDER_CALLBACK(on_tree_selection_changed),
BUILDER_CALLBACK(restore_old_snapshot),
BUILDER_CALLBACK(create_new_snapshot),
BUILDER_CALLBACK(remove_old_snapshot),
BUILDER_CALLBACK(delete_old_snapshot),
BUILDER_CALLBACK(apply_new_snapshot_info),
BUILDER_CALLBACK(close_dialog_box),
NULL);
gtk_builder_connect_signals(builder, builder);
/* Mise à jour de l'interface */
reset_information_area(builder);
update_control_area_access(builder, false, false, false);
combo = GTK_COMBO_BOX(gtk_builder_get_object(builder, "servers"));
gtk_combo_box_set_active(combo, 0);
return result;
}
/******************************************************************************
* *
* Paramètres : dialog = fenêtre en cours de suppression. *
* builder = espace de référencement global. *
* *
* Description : Réagit à une fermeture de la boîte de dialogue. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void on_dialog_destroy(GtkWidget *dialog, GtkBuilder *builder)
{
GAnalystClient *client; /* Cible des interactions */
/* Déconnexion de l'ancien */
client = G_ANALYST_CLIENT(g_object_get_data(G_OBJECT(builder), "client"));
if (client != NULL)
g_signal_handlers_disconnect_by_func(client, on_snapshots_updated, builder);
}
/******************************************************************************
* *
* Paramètres : combo = liste de sélection à l'origine de la procédure. *
* builder = espace de référencement global. *
* *
* Description : Réagit à un changement dans le choix du serveur. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void on_server_selection_changed(GtkComboBox *combo, GtkBuilder *builder)
{
GAnalystClient *client; /* Cible des interactions */
gint active; /* Indice du serveur retenu */
GLoadedBinary *binary; /* Binaire en cours d'étude */
GtkTreeStore *store; /* Modèle de gestion */
/* Déconnexion de l'ancien */
client = G_ANALYST_CLIENT(g_object_get_data(G_OBJECT(builder), "client"));
if (client != NULL)
g_signal_handlers_disconnect_by_func(client, on_snapshots_updated, builder);
/* Connexion nouvelle */
active = gtk_combo_box_get_active(combo);
binary = G_LOADED_BINARY(g_object_get_data(G_OBJECT(builder), "binary"));
client = g_loaded_binary_get_client(binary, active == 0);
if (client == NULL)
{
g_object_set_data(G_OBJECT(builder), "client", NULL);
store = GTK_TREE_STORE(gtk_builder_get_object(builder, "store"));
gtk_tree_store_clear(store);
reset_information_area(builder);
update_control_area_access(builder, false, false, false);
}
else
{
g_object_ref(G_OBJECT(client));
g_object_set_data_full(G_OBJECT(builder), "client", client, g_object_unref);
g_signal_connect(client, "snapshots-updated", G_CALLBACK(on_snapshots_updated), builder);
g_signal_connect(client, "snapshot-changed", G_CALLBACK(on_snapshots_updated), builder);
on_snapshots_updated(client, builder);
}
}
/******************************************************************************
* *
* Paramètres : model = modèle de gestion de données à consulter. *
* iter = point de départ des recherches. *
* id = identifiant de l'instantané recherché. *
* found = emplacement de l'éventuel noeud trouvé. [OUT] *
* *
* Description : Recherche un élément d'arborescence selon un identifiant. *
* *
* Retour : true pour indiquer une recherche fructueuse, false sinon. *
* *
* Remarques : - *
* *
******************************************************************************/
static bool find_suitable_parent(GtkTreeModel *model, GtkTreeIter *iter, const char *id, GtkTreeIter *found)
{
bool result; /* Bilan à retourner */
gchar *value; /* Identifiant courant */
gint count; /* Quantité de fils présents */
gint i; /* Boucle de parcours */
GtkTreeIter child; /* Localisation d'un fils */
#ifndef NDEBUG
gboolean status; /* Bilan d'une consultation */
#endif
gtk_tree_model_get(model, iter, STC_ID, &value, -1);
result = (strcmp(value, id) == 0);
g_free(value);
if (result)
*found = *iter;
else
{
count = gtk_tree_model_iter_n_children(model, iter);
for (i = 0; i < count && !result; i++)
{
#ifndef NDEBUG
status = gtk_tree_model_iter_nth_child(model, &child, iter, i);
assert(status);
#else
gtk_tree_model_iter_nth_child(model, &child, iter, i);
#endif
result = find_suitable_parent(model, &child, id, found);
}
}
return result;
}
/******************************************************************************
* *
* Paramètres : client = client connecté à l'origine de la procédure. *
* builder = espace de référencement global. *
* *
* Description : Met à jour l'affichage avec une nouvelle liste d'instantanés.*
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void on_snapshots_updated(GAnalystClient *client, GtkBuilder *builder)
{
GtkTreeStore *store; /* Modèle de gestion */
snapshot_info_t *info; /* Liste d'instantanés présents*/
size_t count; /* Taille de cette liste */
bool status; /* Validité de cet identifiant */
size_t i; /* Boucle de parcours */
char *id; /* Identifiant du parent */
GtkTreeIter iter; /* Point d'insertion */
#ifndef NDEBUG
gboolean check; /* Vérification par principe */
#endif
GtkTreeIter parent; /* Parent du point d'insertion */
GIcon *icon; /* Image de représentation */
char *name; /* Désignation d'instantané */
snapshot_id_t current; /* Instantané courant */
GtkTreeView *treeview; /* Arborescence graphique */
const char *raw; /* Identifiant brut */
GtkTreeSelection *selection; /* Gestionnaire de sélection */
store = GTK_TREE_STORE(gtk_builder_get_object(builder, "store"));
gtk_tree_store_clear(store);
status = g_analyst_client_get_snapshots(client, &info, &count);
if (!status)
{
reset_information_area(builder);
update_control_area_access(builder, false, false, false);
}
else
{
/* Remplissage */
for (i = 0; i < count; i++)
{
id = snapshot_id_as_string(get_snapshot_info_parent_id(&info[i]));
if (strcmp(id, NO_SNAPSHOT_ROOT) == 0)
gtk_tree_store_append(store, &iter, NULL);
else
{
#ifndef NDEBUG
check = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
assert(check);
#else
gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
#endif
status = find_suitable_parent(GTK_TREE_MODEL(store), &iter, id, &parent);
assert(status);
gtk_tree_store_append(store, &iter, &parent);
}
id = snapshot_id_as_string(get_snapshot_info_id(&info[i]));
icon = G_ICON(g_object_get_data(G_OBJECT(builder), "icon"));
name = get_snapshot_info_name(&info[i]);
gtk_tree_store_set(store, &iter,
STC_ICON, icon,
STC_TITLE, name != NULL ? name : id,
STC_ID, id,
STC_TIMESTAMP, get_snapshot_info_created(&info[i]),
STC_NAME, name,
STC_DESC, get_snapshot_info_desc(&info[i]),
-1);
exit_snapshot_info(&info[i]);
}
free(info);
/* Ajout de l'instantané courant */
status = g_analyst_client_get_current_snapshot(client, ¤t);
if (status)
{
id = snapshot_id_as_string(¤t);
#ifndef NDEBUG
check = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
assert(check);
#else
gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
#endif
status = find_suitable_parent(GTK_TREE_MODEL(store), &iter, id, &parent);
if (status)
{
gtk_tree_store_append(store, &iter, &parent);
icon = G_ICON(g_object_get_data(G_OBJECT(builder), "current_icon"));
gtk_tree_store_set(store, &iter,
STC_ICON, icon,
STC_TITLE, _("Current"),
-1);
}
}
/* Plein affichage */
treeview = GTK_TREE_VIEW(gtk_builder_get_object(builder, "treeview"));
gtk_tree_view_expand_all(treeview);
/* Remise en place de la dernière sélection */
raw = g_object_get_data(G_OBJECT(builder), "selected");
if (raw != NULL)
{
#ifndef NDEBUG
check = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
assert(check);
#else
gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
#endif
status = find_suitable_parent(GTK_TREE_MODEL(store), &iter, raw, &iter);
if (status)
{
selection = gtk_tree_view_get_selection(treeview);
gtk_tree_selection_select_iter(selection, &iter);
}
else
{
reset_information_area(builder);
update_control_area_access(builder, false, false, false);
g_object_set_data(G_OBJECT(builder), "selected", NULL);
}
}
}
}
/******************************************************************************
* *
* Paramètres : builder = espace de référencement global. *
* *
* Description : Réinitialise la zone d'affichage des informations. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void reset_information_area(GtkBuilder *builder)
{
GtkLabel *label; /* Etiquette à faire évoluer */
GtkEntry *entry; /* Zone de saisie à actualiser */
label = GTK_LABEL(gtk_builder_get_object(builder, "identifier"));
gtk_label_set_text(label, "-");
label = GTK_LABEL(gtk_builder_get_object(builder, "timestamp"));
gtk_label_set_text(label, "-");
entry = GTK_ENTRY(gtk_builder_get_object(builder, "name"));
gtk_entry_set_text(entry, "");
entry = GTK_ENTRY(gtk_builder_get_object(builder, "description"));
gtk_entry_set_text(entry, "");
update_information_area_access(builder, false);
}
/******************************************************************************
* *
* Paramètres : builder = espace de référencement global. *
* state = état des possibilités d'interactions à appliquer. *
* *
* Description : Active ou non l'accès à la zone d'affichage des informations.*
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void update_information_area_access(GtkBuilder *builder, bool state)
{
GtkWidget *widget; /* Composant à traiter */
widget = GTK_WIDGET(gtk_builder_get_object(builder, "identifier"));
gtk_widget_set_sensitive(widget, state);
widget = GTK_WIDGET(gtk_builder_get_object(builder, "timestamp"));
gtk_widget_set_sensitive(widget, state);
widget = GTK_WIDGET(gtk_builder_get_object(builder, "name"));
gtk_widget_set_sensitive(widget, state);
widget = GTK_WIDGET(gtk_builder_get_object(builder, "description"));
gtk_widget_set_sensitive(widget, state);
widget = GTK_WIDGET(gtk_builder_get_object(builder, "apply"));
gtk_widget_set_sensitive(widget, state);
}
/******************************************************************************
* *
* Paramètres : builder = espace de référencement global. *
* restore = accès à la restauration d'un instantané. *
* create = accès à la création d'un nouvel instantané. *
* delete = accès à la suppression d'instantanés. *
* *
* Description : Active ou non l'accès à la zone de contrôle des instantanés. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void update_control_area_access(GtkBuilder *builder, bool restore, bool create, bool delete)
{
GtkWidget *widget; /* Composant à traiter */
widget = GTK_WIDGET(gtk_builder_get_object(builder, "restore"));
gtk_widget_set_sensitive(widget, restore);
widget = GTK_WIDGET(gtk_builder_get_object(builder, "create"));
gtk_widget_set_sensitive(widget, create);
widget = GTK_WIDGET(gtk_builder_get_object(builder, "remove"));
gtk_widget_set_sensitive(widget, delete);
widget = GTK_WIDGET(gtk_builder_get_object(builder, "delete"));
gtk_widget_set_sensitive(widget, delete);
}
/******************************************************************************
* *
* Paramètres : selection = nouvelle sélection à appliquer. *
* builder = espace de référencement global. *
* *
* Description : Met à jour l'affichage des informations d'un instantané. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void on_tree_selection_changed(GtkTreeSelection *selection, GtkBuilder *builder)
{
GtkTreeModel *model; /* Modèle de gestion */
GtkTreeIter iter; /* Point de sélection */
gchar *id; /* Identifiant d'instantané */
guint64 timestamp; /* Horodatage associé */
gchar *name; /* Eventuelle désignation */
gchar *desc; /* Eventuelle description */
GtkLabel *label; /* Etiquette à faire évoluer */
char buf[27]; /* Tampon pour la date */
GtkEntry *entry; /* Zone de saisie à actualiser */
gboolean is_not_root; /* Particularité de sélection */
if (gtk_tree_selection_get_selected(selection, &model, &iter))
{
gtk_tree_model_get(model, &iter,
STC_ID, &id,
STC_TIMESTAMP, ×tamp,
STC_NAME, &name,
STC_DESC, &desc,
-1);
if (id == NULL)
{
assert(name == NULL);
assert(desc == NULL);
reset_information_area(builder);
update_control_area_access(builder, false, true, false);
g_object_set_data(G_OBJECT(builder), "selected", NULL);
}
else
{
label = GTK_LABEL(gtk_builder_get_object(builder, "identifier"));
gtk_label_set_text(label, id);
ctime_r((time_t []) { timestamp / 1000000 }, buf);
buf[strlen(buf) - 1] = 0;
label = GTK_LABEL(gtk_builder_get_object(builder, "timestamp"));
gtk_label_set_text(label, buf);
entry = GTK_ENTRY(gtk_builder_get_object(builder, "name"));
gtk_entry_set_text(entry, name != NULL ? name : "");
if (name != NULL)
g_free(name);
entry = GTK_ENTRY(gtk_builder_get_object(builder, "description"));
gtk_entry_set_text(entry, desc != NULL ? desc : "");
if (desc != NULL)
g_free(desc);
update_information_area_access(builder, true);
is_not_root = gtk_tree_model_iter_parent(model, (GtkTreeIter []) { { 0 } }, &iter);
update_control_area_access(builder, true, false, is_not_root);
g_object_set_data_full(G_OBJECT(builder), "selected", id, g_free);
}
}
}
/******************************************************************************
* *
* Paramètres : button = composant GTK à l'origine de la procédure. *
* builder = espace de référencement global. *
* *
* Description : Restaure sur demande un nouvel instantané. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void restore_old_snapshot(GtkToolButton *button, GtkBuilder *builder)
{
const char *raw; /* Identifiant brut */
snapshot_id_t id; /* Identifiant utilisable */
bool status; /* Bilan d'une conversion */
GAnalystClient *client; /* Cible des interactions */
raw = g_object_get_data(G_OBJECT(builder), "selected");
status = init_snapshot_id_from_text(&id, raw);
if (status)
{
client = G_ANALYST_CLIENT(g_object_get_data(G_OBJECT(builder), "client"));
g_analyst_client_restore_snapshot(client, &id);
}
}
/******************************************************************************
* *
* Paramètres : button = composant GTK à l'origine de la procédure. *
* builder = espace de référencement global. *
* *
* Description : Crée sur demande un nouvel instantané. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void create_new_snapshot(GtkToolButton *button, GtkBuilder *builder)
{
GAnalystClient *client; /* Cible des interactions */
client = G_ANALYST_CLIENT(g_object_get_data(G_OBJECT(builder), "client"));
g_analyst_client_create_snapshot(client);
}
/******************************************************************************
* *
* Paramètres : button = composant GTK à l'origine de la procédure. *
* builder = espace de référencement global. *
* *
* Description : Supprime sur demande un instantané. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void remove_old_snapshot(GtkToolButton *button, GtkBuilder *builder)
{
const char *raw; /* Identifiant brut */
snapshot_id_t id; /* Identifiant utilisable */
bool status; /* Bilan d'une conversion */
GAnalystClient *client; /* Cible des interactions */
raw = g_object_get_data(G_OBJECT(builder), "selected");
status = init_snapshot_id_from_text(&id, raw);
if (status)
{
client = G_ANALYST_CLIENT(g_object_get_data(G_OBJECT(builder), "client"));
g_analyst_client_remove_snapshot(client, &id, false);
}
}
/******************************************************************************
* *
* Paramètres : button = composant GTK à l'origine de la procédure. *
* builder = espace de référencement global. *
* *
* Description : Supprime sur demande un instantané et ses successeurs. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void delete_old_snapshot(GtkToolButton *button, GtkBuilder *builder)
{
const char *raw; /* Identifiant brut */
snapshot_id_t id; /* Identifiant utilisable */
bool status; /* Bilan d'une conversion */
GAnalystClient *client; /* Cible des interactions */
raw = g_object_get_data(G_OBJECT(builder), "selected");
status = init_snapshot_id_from_text(&id, raw);
if (status)
{
client = G_ANALYST_CLIENT(g_object_get_data(G_OBJECT(builder), "client"));
g_analyst_client_remove_snapshot(client, &id, true);
}
}
/******************************************************************************
* *
* Paramètres : button = composant GTK à l'origine de la procédure. *
* builder = espace de référencement global. *
* *
* Description : Applique à un instantané ses nouvelles informations. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void apply_new_snapshot_info(GtkButton *button, GtkBuilder *builder)
{
const char *raw; /* Identifiant brut */
snapshot_id_t id; /* Identifiant utilisable */
bool status; /* Bilan d'une conversion */
GAnalystClient *client; /* Cible des interactions */
GtkEntry *entry; /* Zone de saisie à actualiser */
raw = g_object_get_data(G_OBJECT(builder), "selected");
status = init_snapshot_id_from_text(&id, raw);
if (status)
{
client = G_ANALYST_CLIENT(g_object_get_data(G_OBJECT(builder), "client"));
entry = GTK_ENTRY(gtk_builder_get_object(builder, "name"));
g_analyst_client_set_snapshot_name(client, &id, gtk_entry_get_text(entry));
entry = GTK_ENTRY(gtk_builder_get_object(builder, "description"));
g_analyst_client_set_snapshot_desc(client, &id, gtk_entry_get_text(entry));
}
}
/******************************************************************************
* *
* Paramètres : button = composant GTK à l'origine de la procédure. *
* builder = espace de référencement global. *
* *
* Description : Ferme la boîte de dialogue. *
* *
* Retour : - *
* *
* Remarques : - *
* *
******************************************************************************/
static void close_dialog_box(GtkButton *button, GtkBuilder *builder)
{
GtkDialog *dialog; /* Fenêtre à manipuler */
dialog = GTK_DIALOG(gtk_builder_get_object(builder, "window"));
gtk_dialog_response(dialog, GTK_RESPONSE_CLOSE);
}