Add AgDb class
This commit is contained in:
		| @@ -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([ | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
							
								
								
									
										460
									
								
								src/ag-db.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										460
									
								
								src/ag-db.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,460 @@ | ||||
| #include <gio/gio.h> | ||||
| #include <gobject/gobject.h> | ||||
| #include <libgda/libgda.h> | ||||
| #include <sql-parser/gda-sql-parser.h> | ||||
| #include <gobject/gvaluecollector.h> | ||||
|  | ||||
| #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; | ||||
| } | ||||
							
								
								
									
										37
									
								
								src/ag-db.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/ag-db.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| #ifndef __AG_DB_H__ | ||||
| #define __AG_DB_H__ | ||||
|  | ||||
| #include <glib-object.h> | ||||
|  | ||||
| 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__ */ | ||||
		Reference in New Issue
	
	Block a user