From 4d64e707bcb645d92787c718b6ee50cd2941158d Mon Sep 17 00:00:00 2001 From: Gergely Polonkai Date: Sun, 20 Jul 2014 23:13:56 +0200 Subject: [PATCH 01/41] Add AgDb class --- configure.ac | 1 + src/Makefile.am | 5 +- src/ag-db.c | 460 ++++++++++++++++++++++++++++++++++++++++++++++++ src/ag-db.h | 37 ++++ 4 files changed, 501 insertions(+), 2 deletions(-) create mode 100644 src/ag-db.c create mode 100644 src/ag-db.h diff --git a/configure.ac b/configure.ac index c7eb783..d5dfccb 100644 --- a/configure.ac +++ b/configure.ac @@ -35,6 +35,7 @@ PKG_CHECK_MODULES([GTK], [gtk+-3.0 >= 3.8]) PKG_CHECK_MODULES([LIBXML], [libxml-2.0]) PKG_CHECK_MODULES([LIBXSLT], [libexslt]) PKG_CHECK_MODULES([WEBKIT], [webkit2gtk-3.0]) +PKG_CHECK_MODULES([GDA], [libgda-5.0 libgda-sqlite-5.0]) PKG_CHECK_MODULES([SWE_GLIB], [swe-glib >= 2.1.0]) AC_CONFIG_FILES([ diff --git a/src/Makefile.am b/src/Makefile.am index f1e551c..94116d3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -18,6 +18,7 @@ astrognome_source_files = \ ag-chart.c \ ag-settings.c \ ag-preferences.c \ + ag-db.c \ astrognome.c \ $(NULL) @@ -30,7 +31,7 @@ AM_CPPFLAGS = -DG_LOG_DOMAIN=\"Astrognome\" -DLOCALEDIR=\"$(localedir)\" -DPKGDA bin_PROGRAMS = astrognome astrognome_SOURCES = $(astrognome_source_files) $(BUILT_SOURCES) -astrognome_LDADD = $(SWE_GLIB_LIBS) $(GTK_LIBS) $(LIBXML_LIBS) $(LIBXSLT_LIBS) $(WEBKIT_LIBS) +astrognome_LDADD = $(SWE_GLIB_LIBS) $(GTK_LIBS) $(LIBXML_LIBS) $(LIBXSLT_LIBS) $(WEBKIT_LIBS) $(GDA_LIBS) astrognome_LDFLAGS = -rdynamic -astrognome_CFLAGS = $(SWE_GLIB_CFLAGS) $(CFLAGS) $(GTK_CFLAGS) $(LIBXML_CFLAGS) $(LIBXSLT_CFLAGS) $(WEBKIT_CFLAGS) -Wall +astrognome_CFLAGS = $(SWE_GLIB_CFLAGS) $(CFLAGS) $(GTK_CFLAGS) $(LIBXML_CFLAGS) $(LIBXSLT_CFLAGS) $(WEBKIT_CFLAGS) $(GDA_CFLAGS) -Wall diff --git a/src/ag-db.c b/src/ag-db.c new file mode 100644 index 0000000..2e8c953 --- /dev/null +++ b/src/ag-db.c @@ -0,0 +1,460 @@ +#include +#include +#include +#include +#include + +#include "config.h" +#include "ag-db.h" + +#define SCHEMA_VERSION 1 + +static AgDb *singleton = NULL; + +typedef struct _AgDbPrivate { + gchar *dsn; + GdaConnection *conn; +} AgDbPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE(AgDb, ag_db, G_TYPE_OBJECT); + +static void +ag_db_non_select(AgDb *db, const gchar *sql) +{ + GdaStatement *sth; + gint nrows; + const gchar *remain; + GdaSqlParser *parser; + AgDbPrivate *priv = ag_db_get_instance_private(db); + GError *err = NULL; + + parser = g_object_get_data(G_OBJECT(priv->conn), "parser"); + g_assert(GDA_IS_SQL_PARSER(parser)); + + if ((sth = gda_sql_parser_parse_string( + parser, + sql, + &remain, + &err + )) == NULL) { + g_error( + "SQL error: %s", + (err && err->message) + ? err->message + : "no reason" + ); + + if ((nrows = gda_connection_statement_execute_non_select( + priv->conn, + sth, + NULL, + NULL, + &err + )) == -1) { + g_error( + "SQL error: %s", + (err && err->message) + ? err->message + : "no details" + ); + } + + g_object_unref(sth); +} + +static gboolean +ag_db_table_exists(AgDb *db, const gchar *table) +{ + GdaSqlParser *parser; + GdaStatement *sth; + GdaDataModel *result; + GdaSet *params; + GdaHolder *holder; + const gchar *remain; + gboolean ret; + GValue table_value = G_VALUE_INIT; + AgDbPrivate *priv = ag_db_get_instance_private(db); + GError *err = NULL; + + parser = g_object_get_data(G_OBJECT(priv->conn), "parser"); + + if ((sth = gda_sql_parser_parse_string( + parser, + "SELECT type \n" \ + " FROM sqlite_master \n" \ + " WHERE type = 'table' \n" \ + " AND name = ##name::string", + &remain, + &err + )) == NULL) { + g_error( + "SQL error: %s", + (err && err->message) + ? err->message + : "no reason" + ); + } + + if (gda_statement_get_parameters(sth, ¶ms, &err) == FALSE) { + g_error( + "Params error: %s", + (err && err->message) + ? err->message + : "no reason" + ); + } + + holder = gda_set_get_holder(params, "name"); + g_value_init(&table_value, G_TYPE_STRING); + g_value_set_string(&table_value, table); + gda_holder_set_value(holder, &table_value, &err); + g_value_unset(&table_value); + + result = gda_connection_statement_execute_select( + priv->conn, + sth, + params, + &err + ); + + if (gda_data_model_get_n_rows(result) > 0) { + ret = TRUE; + } else { + ret = FALSE; + } + + g_object_unref(result); + g_object_unref(sth); + + return ret; +} + +/** + * ag_db_select: + * @db: the database object to work on + * @err: a #GError or NULL + * @sql: the query to execute + * @...: a NULL terminated list of key-value pairs of the query parameters + * + * Returns: (transfer full): the #GdaDataModel as the result of the query + */ +static GdaDataModel * +ag_db_select(AgDb *db, GError **err, const gchar *sql, ...) +{ + GdaSqlParser *parser; + const gchar *remain; + GdaSet *params; + GdaStatement *sth; + GdaDataModel *ret; + gchar *error = NULL; + AgDbPrivate *priv = ag_db_get_instance_private(db); + + parser = g_object_get_data(G_OBJECT(priv->conn), "parser"); + + if ((sth = gda_sql_parser_parse_string( + parser, + sql, + &remain, + err + )) == NULL) { + g_error( + "SQL error: %s", + (*err && (*err)->message) + ? (*err)->message + : "no reason" + ); + } + + if (!gda_statement_get_parameters(sth, ¶ms, err)) { + g_error( + "SQL error: %s", + (*err && (*err)->message) + ? (*err)->message + : "no reason" + ); + } + + if (params) { + va_list ap; + + va_start(ap, sql); + while (TRUE) { + gchar *key; + GdaHolder *holder; + GType type; + GValue value = G_VALUE_INIT; + + if ((key = va_arg(ap, gchar *)) == NULL) { + break; + } + + if ((holder = gda_set_get_holder( + params, + (const gchar *)key + )) == NULL) { + g_error("Error: holder %s is not defined in query.", key); + } + + type = gda_holder_get_g_type(holder); + g_value_init(&value, type); + G_VALUE_COLLECT_INIT(&value, type, ap, 0, &error); + + if (error) { + g_error("SQL GValue error: %s", error); + } + + if (!gda_holder_set_value(holder, (const GValue *)&value, err)) { + g_error( + "SQL GdaHolder error: %s", + (*err && (*err)->message) + ? (*err)->message + : "no reason" + ); + } + } + } + + ret = gda_connection_statement_execute_select(priv->conn, sth, params, err); + g_object_unref(sth); + + return ret; +} + +static void +ag_db_check_version_table(AgDb *db) +{ + if (ag_db_table_exists(db, "version")) { + GdaDataModel *result; + gint ret; + + g_debug( + "Version table exists. " \ + "Checking if db_version is %d and app_version is %s", + SCHEMA_VERSION, + PACKAGE_VERSION + ); + result = ag_db_select( + db, + NULL, + "SELECT db_version, app_version FROM version", + NULL + ); + + ret = gda_data_model_get_n_rows(result); + + if (ret < 0) { + g_error("No number of rows?"); + } else if (ret > 1) { + g_error("Error in database. Version table has more than one rows!"); + } else if (ret == 0) { + // TODO: Check schema against current one maybe? If it’s fine, we + // may just add a row here. + g_error("Error in database. Version table has no rows!"); + } else { + // Version table has one row + const GValue *value; + GdaDataModelIter *iter = gda_data_model_create_iter(result); + gint version; + + gda_data_model_iter_move_next(iter); + value = gda_data_model_iter_get_value_at(iter, 0); + + if (!G_VALUE_HOLDS_INT(value)) { + g_error( + "Database is invalid. " \ + "version.db_version should be an integer." + ); + } + + version = g_value_get_int(value); + + if (version < SCHEMA_VERSION) { + // TODO + g_error("Update required!"); + } else if (version > SCHEMA_VERSION) { + const GValue *app_version_value; + const gchar *app_version; + + app_version_value = gda_data_model_iter_get_value_at( + iter, + 1 + ); + app_version = g_value_get_string( + app_version_value + ); + + g_error( + "The version of your database is from the future. " \ + "It seems it was created by Astrognome v%s.", + app_version + ); + } else { + g_object_unref(result); + } + } + } else { + GValue db_version = G_VALUE_INIT, + app_version = G_VALUE_INIT; + AgDbPrivate *priv = ag_db_get_instance_private(db); + + g_value_init(&db_version, G_TYPE_INT); + g_value_init(&app_version, G_TYPE_STRING); + + g_value_set_int(&db_version, SCHEMA_VERSION); + g_value_set_static_string(&app_version, PACKAGE_VERSION); + + ag_db_non_select( + db, + "CREATE TABLE version (" \ + "id INTEGER PRIMARY KEY, " \ + "db_version INTEGER UNIQUE NOT NULL, " \ + "app_version TEXT UNIQUE NOT NULL" \ + ")" + ); + + gda_connection_insert_row_into_table( + priv->conn, + "version", + NULL, + "db_version", &db_version, + "app_version", &app_version, + NULL + ); + } +} + +static void +ag_db_check_chart_table(AgDb *db) +{ + ag_db_non_select( + db, + "CREATE TABLE IF NOT EXISTS chart (" \ + "id INTEGER PRIMARY KEY, " \ + "name TEXT NOT NULL, " \ + "country_name TEXT, " \ + "city_name TEXT, " \ + "longitude DOUBLE NOT NULL, " \ + "latitude DOUBLE NOT NULL, " \ + "altitude DOUBLE, " \ + "year INTEGER NOT NULL, " \ + "month UNSIGNED INTEGER NOT NULL, " \ + "day UNSIGNED INTEGER NOT NULL, " \ + "hour UNSIGNED INTEGER NOT NULL, " \ + "minute UNSIGNED INTEGER NOT NULL, " \ + "second UNSIGNED INTEGER NOT NULL, " \ + "timezone DOUBLE NOT NULL, " \ + "house_system TEXT NOT NULL, " \ + "note TEXT" \ + ")" + ); +} + +static gint +ag_db_verify(AgDb *db) +{ + ag_db_check_version_table(db); + ag_db_check_chart_table(db); + + return 0; +} + +static void +ag_db_init(AgDb *db) +{ + GdaSqlParser *parser; + GFile *user_data_dir = g_file_new_for_path(g_get_user_data_dir()), + *ag_data_dir = g_file_get_child(user_data_dir, "astrognome"); + AgDbPrivate *priv = ag_db_get_instance_private(db); + gchar *path = g_file_get_path(ag_data_dir); + GError *err = NULL; + + gda_init(); + + if (!g_file_query_exists(ag_data_dir, NULL)) { + gchar *path = g_file_get_path(ag_data_dir); + + if (g_mkdir_with_parents(path, 0700) != 0) { + g_error( + "Data directory %s does not exist and can not be created.", + path + ); + } + } + + priv->dsn = g_strdup_printf("SQLite://DB_DIR=%s;DB_NAME=charts", path); + + g_free(path); + g_object_unref(user_data_dir); + g_object_unref(ag_data_dir); + + priv->conn = gda_connection_open_from_string( + NULL, + priv->dsn, + NULL, + GDA_CONNECTION_OPTIONS_NONE, + &err + ); + + if (priv->conn == NULL) { + g_error( + "Unable to initialize database: %s", + (err && err->message) + ? err->message + : "no reason" + ); + } + + if ((parser = gda_connection_create_parser(priv->conn)) == NULL) { + parser = gda_sql_parser_new(); + } + + g_object_set_data_full( + G_OBJECT(priv->conn), + "parser", + parser, + g_object_unref + ); + + ag_db_verify(db); +} + +static void +ag_db_dispose(GObject *gobject) +{ + AgDbPrivate *priv = ag_db_get_instance_private(AG_DB(gobject)); + + g_object_unref(priv->conn); + g_free(priv->dsn); + G_OBJECT_CLASS(ag_db_parent_class)->dispose(gobject); +} + +static void +ag_db_finalize(GObject *gobject) +{ + singleton = NULL; + + G_OBJECT_CLASS(ag_db_parent_class)->finalize(gobject); +} + +static void +ag_db_class_init(AgDbClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + + gobject_class->dispose = ag_db_dispose; + gobject_class->finalize = ag_db_finalize; +} + +AgDb * +ag_db_get(void) +{ + if (!singleton) { + singleton = AG_DB(g_object_new(AG_TYPE_DB, NULL)); + } else { + g_object_ref(singleton); + } + + g_assert(singleton); + + return singleton; +} diff --git a/src/ag-db.h b/src/ag-db.h new file mode 100644 index 0000000..5b99040 --- /dev/null +++ b/src/ag-db.h @@ -0,0 +1,37 @@ +#ifndef __AG_DB_H__ +#define __AG_DB_H__ + +#include + +G_BEGIN_DECLS + +#define AG_TYPE_DB (ag_db_get_type()) +#define AG_DB(o) (G_TYPE_CHECK_INSTANCE_CAST((o), \ + AG_TYPE_DB, \ + AgDb)) +#define AG_DB_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), \ + AG_TYPE_DB, \ + AgDbClass)) +#define AG_IS_DB(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), AG_TYPE_DB)) +#define AG_IS_DB_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE((k), AG_TYPE_DB)) +#define AG_DB_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), \ + AG_TYPE_DB, \ + AgDbClass)) + +typedef struct _AgDb AgDb; +typedef struct _AgDbClass AgDbClass; + +struct _AgDb { + GObject parent_instance; +}; + +struct _AgDbClass { + GObjectClass parent_class; +}; + +GType ag_db_get_type(void) G_GNUC_CONST; +AgDb *ag_db_get(void); + +G_END_DECLS + +#endif /* __AG_DB_H__ */ From 0fc41ba54569f64004281e6c17f35e59056a333f Mon Sep 17 00:00:00 2001 From: Gergely Polonkai Date: Sun, 20 Jul 2014 23:13:56 +0200 Subject: [PATCH 02/41] Add missing closing bracket in ag_db_non_select() --- src/ag-db.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ag-db.c b/src/ag-db.c index 2e8c953..0238733 100644 --- a/src/ag-db.c +++ b/src/ag-db.c @@ -43,6 +43,7 @@ ag_db_non_select(AgDb *db, const gchar *sql) ? err->message : "no reason" ); + } if ((nrows = gda_connection_statement_execute_non_select( priv->conn, From 749a253f186d146820a768a59e5c2157363dd988 Mon Sep 17 00:00:00 2001 From: Gergely Polonkai Date: Mon, 21 Jul 2014 23:38:22 +0200 Subject: [PATCH 03/41] Code beautification in ag-chart.h --- src/ag-chart.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/ag-chart.h b/src/ag-chart.h index 35dc89b..e9f8177 100644 --- a/src/ag-chart.h +++ b/src/ag-chart.h @@ -36,13 +36,16 @@ struct _AgChartClass { }; GType ag_chart_get_type(void) G_GNUC_CONST; + AgChart *ag_chart_new_full(GsweTimestamp *timestamp, gdouble longitude, gdouble latitude, gdouble altitude, GsweHouseSystem house_system); + AgChart *ag_chart_load_from_file(GFile *file, GError **err); + void ag_chart_save_to_file(AgChart *chart, GFile *file, GError **err); @@ -53,18 +56,27 @@ void ag_chart_export_svg_to_file(AgChart *chart, void ag_chart_set_name(AgChart *chart, const gchar *name); + gchar *ag_chart_get_name(AgChart *chart); + void ag_chart_set_country(AgChart *chart, const gchar *country); + gchar *ag_chart_get_country(AgChart *chart); + void ag_chart_set_city(AgChart *chart, const gchar *city); + gchar *ag_chart_get_city(AgChart *chart); + gchar *ag_chart_create_svg(AgChart *chart, gsize *length, GError **err); + GList *ag_chart_get_planets(AgChart *chart); + void ag_chart_set_note(AgChart *chart, const gchar *note); + const gchar *ag_chart_get_note(AgChart *chart); #define AG_CHART_ERROR (ag_chart_error_quark()) From 4d718853ea98a34e6ec0966b7b16667eddc3f64e Mon Sep 17 00:00:00 2001 From: Gergely Polonkai Date: Mon, 21 Jul 2014 23:38:33 +0200 Subject: [PATCH 04/41] Add TODO items --- TODO.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/TODO.md b/TODO.md index 0f25cec..67fa260 100644 --- a/TODO.md +++ b/TODO.md @@ -10,6 +10,12 @@ Debian/Ubuntu, Arch or Gentoo. The features below will be implemented in the first release of Astrognome. +* Implement storage database. Database should contain a small preview image of + the chart. The selection screen should show a small box with the name and the + preview image. Upon mouseover it should blur/darken the image, and write some + chart data over it, like birth date and place +* Placidus .HOR import +* Chart cleanup and manipulation * Generate, load and save synastries * Calculate transits and progressions * Stepping through time without modifying the chart data @@ -26,6 +32,7 @@ The features below will be implemented in the first release of Astrognome. * Display hidden ascendent * Display Vertex/anti-Vertex * Symbol of Uranus and Pluto to use in charts +* Regiomontanus import ## Later features From f46e35430ec54b75ccb0f9a6e1593f406446fa23 Mon Sep 17 00:00:00 2001 From: Gergely Polonkai Date: Mon, 21 Jul 2014 23:39:04 +0200 Subject: [PATCH 05/41] Add note to example chart --- data/examples/saved-chart.agc | 1 + 1 file changed, 1 insertion(+) diff --git a/data/examples/saved-chart.agc b/data/examples/saved-chart.agc index ee29b54..04328a6 100644 --- a/data/examples/saved-chart.agc +++ b/data/examples/saved-chart.agc @@ -20,4 +20,5 @@ placidus + This is my own chart. From 98d08893efb392e9f1fc8e10adf74da195d5de75 Mon Sep 17 00:00:00 2001 From: Gergely Polonkai Date: Mon, 21 Jul 2014 23:18:18 +0200 Subject: [PATCH 06/41] Add struct AgDbSave together with the function ag_db_save_data_free() --- src/ag-db.c | 30 ++++++++++++++++++++++++++++++ src/ag-db.h | 22 ++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/src/ag-db.c b/src/ag-db.c index 0238733..1725265 100644 --- a/src/ag-db.c +++ b/src/ag-db.c @@ -459,3 +459,33 @@ ag_db_get(void) return singleton; } + +void +ag_db_save_data_free(AgDbSave *save_data) +{ + if (!save_data) { + return; + } + + if (save_data->name) { + g_free(save_data->name); + } + + if (save_data->country) { + g_free(save_data->country); + } + + if (save_data->city) { + g_free(save_data->city); + } + + if (save_data->house_system) { + g_free(save_data->house_system); + } + + if (save_data->note) { + g_free(save_data->note); + } + + g_free(save_data); +} diff --git a/src/ag-db.h b/src/ag-db.h index 5b99040..48cd352 100644 --- a/src/ag-db.h +++ b/src/ag-db.h @@ -29,9 +29,31 @@ struct _AgDbClass { GObjectClass parent_class; }; +typedef struct _AgDbSave { + gint db_id; + gchar *name; + gchar *country; + gchar *city; + gdouble longitude; + gdouble latitude; + gdouble altitude; + gint year; + guint month; + guint day; + guint hour; + guint minute; + guint second; + gdouble timezone; + gchar *house_system; + gchar *note; +} AgDbSave; + GType ag_db_get_type(void) G_GNUC_CONST; + AgDb *ag_db_get(void); +void ag_db_save_data_free(AgDbSave *save_data); + G_END_DECLS #endif /* __AG_DB_H__ */ From e8d9386916d6e21b8435d4ddf4baf8702ba9f09e Mon Sep 17 00:00:00 2001 From: Gergely Polonkai Date: Mon, 21 Jul 2014 23:33:28 +0200 Subject: [PATCH 07/41] Add function ag_db_save_chart() to add/update charts in the database --- src/ag-db.c | 159 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/ag-db.h | 6 ++ 2 files changed, 165 insertions(+) diff --git a/src/ag-db.c b/src/ag-db.c index 1725265..6a92317 100644 --- a/src/ag-db.c +++ b/src/ag-db.c @@ -5,6 +5,7 @@ #include #include "config.h" +#include "ag-app.h" #include "ag-db.h" #define SCHEMA_VERSION 1 @@ -489,3 +490,161 @@ ag_db_save_data_free(AgDbSave *save_data) g_free(save_data); } + +gboolean +ag_db_save_chart(AgDb *db, AgDbSave *save_data, GtkWidget *window, GError **err) +{ + GValue db_id = G_VALUE_INIT, + name = G_VALUE_INIT, + country = G_VALUE_INIT, + city = G_VALUE_INIT, + longitude = G_VALUE_INIT, + latitude = G_VALUE_INIT, + altitude = G_VALUE_INIT, + year = G_VALUE_INIT, + month = G_VALUE_INIT, + day = G_VALUE_INIT, + hour = G_VALUE_INIT, + minute = G_VALUE_INIT, + second = G_VALUE_INIT, + timezone = G_VALUE_INIT, + house_system = G_VALUE_INIT, + note = G_VALUE_INIT; + AgDbPrivate *priv = ag_db_get_instance_private(db); + + g_value_init(&name, G_TYPE_STRING); + g_value_set_string(&name, save_data->name); + + g_value_init(&country, G_TYPE_STRING); + g_value_set_string(&country, save_data->country); + + g_value_init(&city, G_TYPE_STRING); + g_value_set_string(&city, save_data->city); + + g_value_init(&longitude, G_TYPE_DOUBLE); + g_value_set_double(&longitude, save_data->longitude); + + g_value_init(&latitude, G_TYPE_DOUBLE); + g_value_set_double(&latitude, save_data->latitude); + + g_value_init(&altitude, G_TYPE_DOUBLE); + g_value_set_double(&altitude, save_data->altitude); + + g_value_init(&year, G_TYPE_INT); + g_value_set_int(&year, save_data->year); + + g_value_init(&month, G_TYPE_UINT); + g_value_set_uint(&month, save_data->month); + + g_value_init(&day, G_TYPE_UINT); + g_value_set_uint(&day, save_data->day); + + g_value_init(&hour, G_TYPE_UINT); + g_value_set_uint(&hour, save_data->hour); + + g_value_init(&minute, G_TYPE_UINT); + g_value_set_uint(&minute, save_data->minute); + + g_value_init(&second, G_TYPE_UINT); + g_value_set_uint(&second, save_data->second); + + g_value_init(&timezone, G_TYPE_DOUBLE); + g_value_set_double(&timezone, save_data->timezone); + + g_value_init(&house_system, G_TYPE_STRING); + g_value_set_string(&house_system, save_data->house_system); + + g_value_init(¬e, G_TYPE_STRING); + g_value_set_string(¬e, save_data->note); + + if (save_data->db_id == -1) { + if (!gda_connection_insert_row_into_table( + priv->conn, + "chart", + err, + "name", &name, + "country_name", &country, + "city_name", &city, + "longitude", &longitude, + "latitude", &latitude, + "altitude", &altitude, + "year", &year, + "month", &month, + "day", &day, + "hour", &hour, + "minute", &minute, + "second", &second, + "timezone", &timezone, + "house_system", &house_system, + "note", ¬e, + NULL + )) { + + ag_app_message_dialog( + window, + GTK_MESSAGE_ERROR, + "Unable to save: %s", + (*err && (*err)->message) + ? (*err)->message + : "no reason" + ); + } + } else { + g_value_init(&db_id, G_TYPE_INT); + g_value_set_int(&db_id, save_data->db_id); + + if (!gda_connection_update_row_in_table( + priv->conn, + "chart", + "id", + &db_id, + err, + "name", &name, + "country_name", &country, + "city_name", &city, + "longitude", &longitude, + "latitude", &latitude, + "altitude", &altitude, + "year", &year, + "month", &month, + "day", &day, + "hour", &hour, + "minute", &minute, + "second", &second, + "timezone", &timezone, + "house_system", &house_system, + "note", ¬e, + NULL + )) { + + ag_app_message_dialog( + window, + GTK_MESSAGE_ERROR, + "Unable to save: %s", + (*err && (*err)->message) + ? (*err)->message + : "no reason" + ); + } + + g_value_unset(&db_id); + } + + g_value_unset(¬e); + g_value_unset(&house_system); + g_value_unset(&timezone); + g_value_unset(&second); + g_value_unset(&minute); + g_value_unset(&hour); + g_value_unset(&day); + g_value_unset(&month); + g_value_unset(&year); + g_value_unset(&altitude); + g_value_unset(&latitude); + g_value_unset(&longitude); + g_value_unset(&city); + g_value_unset(&country); + g_value_unset(&name); + + return FALSE; +} diff --git a/src/ag-db.h b/src/ag-db.h index 48cd352..efe82aa 100644 --- a/src/ag-db.h +++ b/src/ag-db.h @@ -2,6 +2,7 @@ #define __AG_DB_H__ #include +#include G_BEGIN_DECLS @@ -54,6 +55,11 @@ AgDb *ag_db_get(void); void ag_db_save_data_free(AgDbSave *save_data); +gboolean ag_db_save_chart(AgDb *db, + AgDbSave *save_data, + GtkWidget *window, + GError **err); + G_END_DECLS #endif /* __AG_DB_H__ */ From 8a654e9c0f5244a8f7f46feb6b46e9df12cf97e1 Mon Sep 17 00:00:00 2001 From: Gergely Polonkai Date: Tue, 22 Jul 2014 12:25:00 +0200 Subject: [PATCH 08/41] Add function ag_db_get_chart_list() --- src/ag-db.c | 34 ++++++++++++++++++++++++++++++++++ src/ag-db.h | 2 ++ 2 files changed, 36 insertions(+) diff --git a/src/ag-db.c b/src/ag-db.c index 6a92317..42f0b19 100644 --- a/src/ag-db.c +++ b/src/ag-db.c @@ -648,3 +648,37 @@ ag_db_save_chart(AgDb *db, AgDbSave *save_data, GtkWidget *window, GError **err) return FALSE; } + +GList * +ag_db_get_chart_list(AgDb *db, GError **err) +{ + GdaDataModelIter *iter; + GList *ret = NULL; + GdaDataModel *result = ag_db_select( + db, + err, + "SELECT id, name FROM chart ORDER BY name", + NULL + ); + + if (result == NULL) { + return NULL; + } + + iter = gda_data_model_create_iter(result); + + while (gda_data_model_iter_move_next(iter)) { + const GValue *value; + AgDbSave *save_data = g_new0(AgDbSave, 1); + + value = gda_data_model_iter_get_value_at(iter, 0); + save_data->db_id = g_value_get_int(value); + + value = gda_data_model_iter_get_value_at(iter, 1); + save_data->name = g_strdup(g_value_get_string(value)); + + ret = g_list_prepend(ret, save_data); + } + + return g_list_reverse(ret); +} diff --git a/src/ag-db.h b/src/ag-db.h index efe82aa..3ac3447 100644 --- a/src/ag-db.h +++ b/src/ag-db.h @@ -60,6 +60,8 @@ gboolean ag_db_save_chart(AgDb *db, GtkWidget *window, GError **err); +GList *ag_db_get_chart_list(AgDb *db, GError **err); + G_END_DECLS #endif /* __AG_DB_H__ */ From bfa4cf8ccaf9d78488c3e69c86f2496e2cd982e7 Mon Sep 17 00:00:00 2001 From: Gergely Polonkai Date: Thu, 31 Jul 2014 00:19:09 +0200 Subject: [PATCH 09/41] Add AgDbError error type --- src/ag-db.c | 2 ++ src/ag-db.h | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/src/ag-db.c b/src/ag-db.c index 42f0b19..7cf9422 100644 --- a/src/ag-db.c +++ b/src/ag-db.c @@ -17,6 +17,8 @@ typedef struct _AgDbPrivate { GdaConnection *conn; } AgDbPrivate; +G_DEFINE_QUARK(ag_db_error_quark, ag_db_error); + G_DEFINE_TYPE_WITH_PRIVATE(AgDb, ag_db, G_TYPE_OBJECT); static void diff --git a/src/ag-db.h b/src/ag-db.h index 3ac3447..104ad3b 100644 --- a/src/ag-db.h +++ b/src/ag-db.h @@ -49,6 +49,10 @@ typedef struct _AgDbSave { gchar *note; } AgDbSave; +typedef enum { + AG_DB_ERROR_NO_CHART +} AgDbError; + GType ag_db_get_type(void) G_GNUC_CONST; AgDb *ag_db_get(void); @@ -62,6 +66,9 @@ gboolean ag_db_save_chart(AgDb *db, GList *ag_db_get_chart_list(AgDb *db, GError **err); +#define AG_DB_ERROR (ag_db_error_quark()) +GQuark ag_db_error_quark(void); + G_END_DECLS #endif /* __AG_DB_H__ */ From ea9e4a73a559af0adcfb393e033b1d06b4f25ff9 Mon Sep 17 00:00:00 2001 From: Gergely Polonkai Date: Thu, 31 Jul 2014 00:33:54 +0200 Subject: [PATCH 10/41] Add function ag_db_get_chart_data_by_id() It loads one single chart from the database and fills an AgDbSave struct with its data --- src/ag-db.c | 218 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/ag-db.h | 2 + 2 files changed, 220 insertions(+) diff --git a/src/ag-db.c b/src/ag-db.c index 7cf9422..8473d84 100644 --- a/src/ag-db.c +++ b/src/ag-db.c @@ -21,6 +21,52 @@ G_DEFINE_QUARK(ag_db_error_quark, ag_db_error); G_DEFINE_TYPE_WITH_PRIVATE(AgDb, ag_db, G_TYPE_OBJECT); +typedef enum { + COLUMN_ID, + COLUMN_NAME, + COLUMN_COUNTRY, + COLUMN_CITY, + COLUMN_LONGITUDE, + COLUMN_LATITUDE, + COLUMN_ALTITUDE, + COLUMN_YEAR, + COLUMN_MONTH, + COLUMN_DAY, + COLUMN_HOUR, + COLUMN_MINUTE, + COLUMN_SECOND, + COLUMN_TIMEZONE, + COLUMN_HOUSE_SYSTEM, + COLUMN_NOTE, + + /* Leave this as the last element */ + COLUMN_COUNT +} ChartTableColumn; + +typedef struct { + ChartTableColumn id; + gchar *name; +} ChartTableColumnDef; + +static ChartTableColumnDef chart_table_column[] = { + { COLUMN_ID, "id" }, + { COLUMN_NAME, "name" }, + { COLUMN_COUNTRY, "country_name" }, + { COLUMN_CITY, "city_name" }, + { COLUMN_LONGITUDE, "longitude" }, + { COLUMN_LATITUDE, "latitude" }, + { COLUMN_ALTITUDE, "altitude" }, + { COLUMN_YEAR, "year" }, + { COLUMN_MONTH, "month" }, + { COLUMN_DAY, "day" }, + { COLUMN_HOUR, "hour" }, + { COLUMN_MINUTE, "minute" }, + { COLUMN_SECOND, "second" }, + { COLUMN_TIMEZONE, "timezone" }, + { COLUMN_HOUSE_SYSTEM, "house_system" }, + { COLUMN_NOTE, "note" }, +}; + static void ag_db_non_select(AgDb *db, const gchar *sql) { @@ -684,3 +730,175 @@ ag_db_get_chart_list(AgDb *db, GError **err) return g_list_reverse(ret); } + +AgDbSave * +ag_db_get_chart_data_by_id(AgDb *db, guint row_id, GError **err) +{ + AgDbSave *save_data; + const GValue *value; + GdaValueAttribute attributes; + gchar *query, + *columns; + guint i; + GdaDataModel *result; + GError *local_err = NULL; + + columns = NULL; + + for (i = 1; i < COLUMN_COUNT; i++) { + gchar *tmp; + + if (i == 1) { + columns = g_strjoin( + ", ", + chart_table_column[0].name, + chart_table_column[1].name, + NULL + ); + } else { + tmp = g_strjoin(", ", columns, chart_table_column[i].name, NULL); + g_free(columns); + columns = tmp; + } + } + + query = g_strdup_printf( + "SELECT %s FROM chart WHERE id = ##id::gint", + columns + ); + g_free(columns); + + result = ag_db_select(db, &local_err, query, "id", row_id, NULL); + g_free(query); + + if (local_err && (local_err->message)) { + return NULL; + } + + if (gda_data_model_get_n_rows(result) < 1) { + g_set_error( + err, + AG_DB_ERROR, AG_DB_ERROR_NO_CHART, + "Chart does not exist" + ); + + return NULL; + } + + save_data = g_new0(AgDbSave, 1); + + /* id */ + value = gda_data_model_get_value_at(result, COLUMN_ID, 0, NULL); + save_data->db_id = g_value_get_int(value); + + /* name */ + value = gda_data_model_get_value_at(result, COLUMN_NAME, 0, NULL); + save_data->name = g_strdup(g_value_get_string(value)); + + /* country */ + value = gda_data_model_get_value_at(result, COLUMN_COUNTRY, 0, NULL); + attributes = gda_data_model_get_attributes_at(result, COLUMN_COUNTRY, 0); + + if (attributes | GDA_VALUE_ATTR_IS_NULL) { + save_data->country = NULL; + } else { + save_data->country = g_strdup(g_value_get_string(value)); + } + + value = gda_data_model_get_value_at(result, COLUMN_CITY, 0, NULL); + attributes = gda_data_model_get_attributes_at(result, COLUMN_CITY, 0); + + if (attributes | GDA_VALUE_ATTR_IS_NULL) { + save_data->city = NULL; + } else { + save_data->city = g_strdup(g_value_get_string(value)); + } + + value = gda_data_model_get_value_at( + result, + COLUMN_LONGITUDE, + 0, + NULL + ); + save_data->longitude = g_value_get_double(value); + + value = gda_data_model_get_value_at( + result, + COLUMN_LATITUDE, + 0, + NULL + ); + save_data->latitude = g_value_get_double(value); + + value = gda_data_model_get_value_at(result, COLUMN_ALTITUDE, 0, NULL); + attributes = gda_data_model_get_attributes_at(result, COLUMN_ALTITUDE, 0); + + if (attributes | GDA_VALUE_ATTR_IS_NULL) { + /* TODO: this value should be macroified */ + save_data->altitude = 280.0; + } else { + save_data->altitude = g_value_get_double(value); + } + + value = gda_data_model_get_value_at(result, COLUMN_YEAR, 0, NULL); + save_data->year = g_value_get_int(value); + + value = gda_data_model_get_value_at( + result, + COLUMN_MONTH, + 0, + NULL + ); + save_data->month = g_value_get_uint(value); + + value = gda_data_model_get_value_at(result, COLUMN_DAY, 0, NULL); + save_data->day = g_value_get_uint(value); + + value = gda_data_model_get_value_at(result, COLUMN_HOUR, 0, NULL); + save_data->hour = g_value_get_uint(value); + + value = gda_data_model_get_value_at( + result, + COLUMN_MINUTE, + 0, + NULL + ); + save_data->minute = g_value_get_uint(value); + + value = gda_data_model_get_value_at( + result, + COLUMN_SECOND, + 0, + NULL + ); + save_data->second = g_value_get_uint(value); + + value = gda_data_model_get_value_at( + result, + COLUMN_TIMEZONE, + 0, + NULL + ); + save_data->timezone = g_value_get_double(value); + + value = gda_data_model_get_value_at( + result, + COLUMN_HOUSE_SYSTEM, + 0, + NULL + ); + save_data->house_system = g_strdup(g_value_get_string(value)); + + value = gda_data_model_get_value_at(result, COLUMN_NOTE, 0, NULL); + attributes = gda_data_model_get_attributes_at(result, 15, 0); + + if (attributes | GDA_VALUE_ATTR_IS_NULL) { + save_data->note = NULL; + } else { + save_data->note = g_strdup(g_value_get_string(value)); + } + + g_object_unref(result); + + return save_data; +} diff --git a/src/ag-db.h b/src/ag-db.h index 104ad3b..47ddb26 100644 --- a/src/ag-db.h +++ b/src/ag-db.h @@ -66,6 +66,8 @@ gboolean ag_db_save_chart(AgDb *db, GList *ag_db_get_chart_list(AgDb *db, GError **err); +AgDbSave *ag_db_get_chart_data_by_id(AgDb *db, guint row_id, GError **err); + #define AG_DB_ERROR (ag_db_error_quark()) GQuark ag_db_error_quark(void); From bee9ae363fe341c86072ddd2fb95af52b35bcace Mon Sep 17 00:00:00 2001 From: Gergely Polonkai Date: Thu, 31 Jul 2014 00:34:51 +0200 Subject: [PATCH 11/41] Fix ag_db_select() Using parameter @err for error collection was a bad idea. Creating local_err instead. --- src/ag-db.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/ag-db.c b/src/ag-db.c index 8473d84..1772016 100644 --- a/src/ag-db.c +++ b/src/ag-db.c @@ -196,8 +196,9 @@ ag_db_select(AgDb *db, GError **err, const gchar *sql, ...) GdaSet *params; GdaStatement *sth; GdaDataModel *ret; - gchar *error = NULL; - AgDbPrivate *priv = ag_db_get_instance_private(db); + gchar *error = NULL; + GError *local_err = NULL; + AgDbPrivate *priv = ag_db_get_instance_private(db); parser = g_object_get_data(G_OBJECT(priv->conn), "parser"); @@ -205,21 +206,21 @@ ag_db_select(AgDb *db, GError **err, const gchar *sql, ...) parser, sql, &remain, - err + &local_err )) == NULL) { g_error( "SQL error: %s", - (*err && (*err)->message) - ? (*err)->message + (local_err && local_err->message) + ? local_err->message : "no reason" ); } - if (!gda_statement_get_parameters(sth, ¶ms, err)) { + if (!gda_statement_get_parameters(sth, ¶ms, &local_err)) { g_error( "SQL error: %s", - (*err && (*err)->message) - ? (*err)->message + (local_err && local_err->message) + ? local_err->message : "no reason" ); } From 310caa929294b3b079923b8df919ac6cf035d015 Mon Sep 17 00:00:00 2001 From: Gergely Polonkai Date: Thu, 31 Jul 2014 00:37:17 +0200 Subject: [PATCH 12/41] Add function ag_chart_new_from_db_save() It constructs an AgChart from an AgDbSave struct --- src/ag-chart.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/ag-chart.h | 6 +++++ 2 files changed, 70 insertions(+) diff --git a/src/ag-chart.c b/src/ag-chart.c index 43675fa..f2619d6 100644 --- a/src/ag-chart.c +++ b/src/ag-chart.c @@ -920,6 +920,70 @@ ag_chart_load_from_file(GFile *file, GError **err) return chart; } +AgChart * +ag_chart_new_from_db_save(AgDbSave *save_data, GError **err) +{ + GsweTimestamp *timestamp; + gchar *house_system_enum_name; + GTypeClass *house_system_class; + GEnumValue *enum_value; + GsweHouseSystem house_system; + AgChart *chart; + + if (save_data == NULL) { + g_set_error( + err, + AG_CHART_ERROR, AG_CHART_ERROR_EMPTY_RECORD, + "Invalid chart" + ); + + return NULL; + } + + house_system_enum_name = g_utf8_strdown(save_data->house_system, -1); + house_system_class = g_type_class_ref(GSWE_TYPE_HOUSE_SYSTEM); + + if ((enum_value = g_enum_get_value_by_nick( + G_ENUM_CLASS(house_system_class), + house_system_enum_name + )) == NULL) { + g_free(house_system_enum_name); + g_set_error( + err, + AG_CHART_ERROR, AG_CHART_ERROR_INVALID_HOUSE_SYSTEM, + "Invalid house system: '%s'", + save_data->house_system + ); + + return NULL; + } + + g_free(house_system_enum_name); + + house_system = enum_value->value; + + timestamp = gswe_timestamp_new_from_gregorian_full( + save_data->year, save_data->month, save_data->day, + save_data->hour, save_data->minute, save_data->second, 0, + save_data->timezone + ); + + chart = ag_chart_new_full( + timestamp, + save_data->longitude, + save_data->latitude, + save_data->altitude, + house_system + ); + + ag_chart_set_name(chart, save_data->name); + ag_chart_set_country(chart, save_data->country); + ag_chart_set_city(chart, save_data->city); + ag_chart_set_note(chart, save_data->note); + + return chart; +} + static xmlDocPtr create_save_doc(AgChart *chart) { diff --git a/src/ag-chart.h b/src/ag-chart.h index e9f8177..ab6ac3e 100644 --- a/src/ag-chart.h +++ b/src/ag-chart.h @@ -4,11 +4,15 @@ #include #include +#include "ag-db.h" + G_BEGIN_DECLS typedef enum { AG_CHART_ERROR_LIBXML, AG_CHART_ERROR_CORRUPT_FILE, + AG_CHART_ERROR_EMPTY_RECORD, + AG_CHART_ERROR_INVALID_HOUSE_SYSTEM, } AgChartError; #define AG_TYPE_CHART (ag_chart_get_type()) @@ -46,6 +50,8 @@ AgChart *ag_chart_new_full(GsweTimestamp *timestamp, AgChart *ag_chart_load_from_file(GFile *file, GError **err); +AgChart *ag_chart_new_from_db_save(AgDbSave *save_data, GError **err); + void ag_chart_save_to_file(AgChart *chart, GFile *file, GError **err); From e2606ea5ebae424da994280a754da45611ef7fdb Mon Sep 17 00:00:00 2001 From: Gergely Polonkai Date: Thu, 31 Jul 2014 00:45:16 +0200 Subject: [PATCH 13/41] Add list store model for the GdMainView --- src/ag-window.c | 5 +++++ src/resources/ui/ag-window.ui | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/ag-window.c b/src/ag-window.c index acc8354..2dfd68d 100644 --- a/src/ag-window.c +++ b/src/ag-window.c @@ -1070,6 +1070,11 @@ ag_window_class_init(AgWindowClass *klass) AgWindow, header_bar ); + gtk_widget_class_bind_template_child_private( + widget_class, + AgWindow, + db_chart_data + ); gtk_widget_class_bind_template_child_private(widget_class, AgWindow, name); gtk_widget_class_bind_template_child_private(widget_class, AgWindow, year); gtk_widget_class_bind_template_child_private(widget_class, AgWindow, month); diff --git a/src/resources/ui/ag-window.ui b/src/resources/ui/ag-window.ui index 2deeeaf..a91a689 100644 --- a/src/resources/ui/ag-window.ui +++ b/src/resources/ui/ag-window.ui @@ -116,6 +116,26 @@ + + + + + + + + + + + + + + + + + + + +