#include #include #include #include #include #include #include const char *const SQLITE_PATH_FORMAT_STRING = "%s/.local/var/openmg.sqlite"; struct _MgDatabase { GObject parent_instance; sqlite3 *sqlite; }; G_DEFINE_TYPE (MgDatabase, mg_database, G_TYPE_OBJECT) typedef enum { MG_DATABASE_SQLITE = 1, MG_DATABASE_N_PROPERTIES } MgDatabaseProperties; static GParamSpec *database_properties[MG_DATABASE_N_PROPERTIES] = { NULL, }; static void mg_database_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void mg_database_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void mg_database_dispose (GObject *object); static gboolean mg_database_apply_single_migration (MgDatabase *self, sqlite3 *sqlite, int migration_number); static int mg_database_retrieve_next_migration (MgDatabase *self, sqlite3 *sqlite); static gboolean mg_database_register_next_migration (MgDatabase *self, sqlite3 *sqlite, int next_migration); static void mg_database_apply_migrations (MgDatabase *self, sqlite3 *sqlite); MgDatabase * mg_database_new (void) { MgDatabase *self = NULL; sqlite3 *sqlite_handle = NULL; const char *home_dir = g_get_home_dir(); size_t database_path_len; char *database_path; char *database_path_directory; database_path_len = snprintf (NULL, 0, SQLITE_PATH_FORMAT_STRING, home_dir); database_path = g_malloc (sizeof *database_path * (database_path_len + 1)); snprintf (database_path, database_path_len, SQLITE_PATH_FORMAT_STRING, home_dir); database_path_directory = g_path_get_dirname (database_path); // TODO: Control the possible error. g_mkdir_with_parents (database_path_directory, 00700); sqlite3_open (database_path, &sqlite_handle); g_free (database_path); self = MG_DATABASE ((g_object_new (MG_TYPE_DATABASE, "sqlite", sqlite_handle, NULL))); return self; } static void mg_database_class_init (MgDatabaseClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); object_class->set_property = mg_database_set_property; object_class->get_property = mg_database_get_property; object_class->dispose = mg_database_dispose; database_properties[MG_DATABASE_SQLITE] = g_param_spec_pointer ( "sqlite", "SQLite", "SQLite handle.", G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); g_object_class_install_properties (object_class, MG_DATABASE_N_PROPERTIES, database_properties); } static void mg_database_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { MgDatabase *self = MG_DATABASE (object); switch ((MgDatabaseProperties) property_id) { case MG_DATABASE_SQLITE: if (self->sqlite) { sqlite3_close (self->sqlite); } self->sqlite = g_value_get_pointer (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void mg_database_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { MgDatabase *self = MG_DATABASE (object); switch ((MgDatabaseProperties) property_id) { case MG_DATABASE_SQLITE: g_value_set_pointer (value, self->sqlite); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void mg_database_init (MgDatabase *self) { } static sqlite3 * mg_database_get_sqlite (MgDatabase *self) { sqlite3 *sqlite; GValue value = G_VALUE_INIT; g_value_init (&value, G_TYPE_POINTER); g_object_get_property (G_OBJECT (self), "sqlite", &value); sqlite = g_value_get_pointer (&value); g_value_unset (&value); mg_database_apply_migrations (self, sqlite); return sqlite; } static void mg_database_apply_migrations (MgDatabase *self, sqlite3 *sqlite) { size_t migrations_len = sizeof MIGRATIONS / sizeof *MIGRATIONS; int next_migration = mg_database_retrieve_next_migration (self, sqlite); for (int i = next_migration; i < migrations_len; i++) { if (!mg_database_apply_single_migration (self, sqlite, next_migration)) { return; } next_migration++; mg_database_register_next_migration (self, sqlite, next_migration); } } /* * Returns affected rows. */ static int mg_database_register_next_migration_atttempt_insert (MgDatabase *self, sqlite3 *sqlite, int next_migration) { sqlite3_stmt *statement = NULL; int return_value = 0; int error = sqlite3_prepare_v2 (sqlite, "insert into options (key, value) values (?, ?);", -1, &statement, NULL); if (error != SQLITE_OK) { g_warning (sqlite3_errmsg (sqlite)); goto cleanup_mg_database_register_next_migration_attempt_insert; } error = sqlite3_bind_text (statement, 1, "migration", -1, SQLITE_TRANSIENT); if (error != SQLITE_OK) { g_warning (sqlite3_errmsg (sqlite)); goto cleanup_mg_database_register_next_migration_attempt_insert; } error = sqlite3_bind_int (statement, 2, next_migration); if (error != SQLITE_OK) { g_warning (sqlite3_errmsg (sqlite)); goto cleanup_mg_database_register_next_migration_attempt_insert; } error = sqlite3_step (statement); if (error != SQLITE_DONE) { g_warning (sqlite3_errmsg (sqlite)); goto cleanup_mg_database_register_next_migration_attempt_insert; } return_value = sqlite3_changes (sqlite); cleanup_mg_database_register_next_migration_attempt_insert: sqlite3_finalize (statement); return return_value; } static int mg_database_register_next_migration_update (MgDatabase *self, sqlite3 *sqlite, int next_migration) { int return_value = 0; sqlite3_stmt *statement = NULL; int error = sqlite3_prepare_v2 (sqlite, "update options set value = ? where key = ?;", -1, &statement, NULL); if (error != SQLITE_OK) { g_warning (sqlite3_errmsg (sqlite)); goto cleanup_mg_database_register_next_migration_update; } error = sqlite3_bind_int (statement, 1, next_migration); if (error != SQLITE_OK) { g_warning (sqlite3_errmsg (sqlite)); goto cleanup_mg_database_register_next_migration_update; } error = sqlite3_bind_text (statement, 2, "migration", -1, SQLITE_TRANSIENT); if (error != SQLITE_OK) { g_warning (sqlite3_errmsg (sqlite)); goto cleanup_mg_database_register_next_migration_update; } error = sqlite3_step (statement); if (error != SQLITE_DONE) { g_warning (sqlite3_errmsg (sqlite)); goto cleanup_mg_database_register_next_migration_update; } return_value = sqlite3_changes (sqlite); cleanup_mg_database_register_next_migration_update: sqlite3_finalize (statement); return return_value; } static gboolean mg_database_register_next_migration (MgDatabase *self, sqlite3 *sqlite, int next_migration) { int affected_rows = mg_database_register_next_migration_atttempt_insert (self, sqlite, next_migration); if (affected_rows) { return 1; } return !!mg_database_register_next_migration_update (self, sqlite, next_migration); } static int mg_database_retrieve_next_migration (MgDatabase *self, sqlite3 *sqlite) { int next_migration = 0; int error; sqlite3_stmt *statement = NULL; error = sqlite3_prepare_v2 (sqlite, "select value from options where key = ?;", -1, &statement, NULL); if (error != SQLITE_OK) { g_warning (sqlite3_errmsg (sqlite)); goto cleanup_mg_database_retrieve_next_migration; } error = sqlite3_bind_text (statement, 1, "migration", -1, SQLITE_TRANSIENT); if (error != SQLITE_OK) { g_warning (sqlite3_errmsg (sqlite)); goto cleanup_mg_database_retrieve_next_migration; } error = sqlite3_step (statement); if (error == SQLITE_ROW) { next_migration = sqlite3_column_int (statement, 0); } cleanup_mg_database_retrieve_next_migration: sqlite3_finalize (statement); return next_migration; } static gboolean mg_database_apply_single_migration (MgDatabase *self, sqlite3 *sqlite, int migration_number) { sqlite3_stmt *statement = NULL; const char *current_migration = MIGRATIONS[migration_number]; gboolean return_value = 0; int error = sqlite3_prepare_v2 (sqlite, current_migration, -1, &statement, NULL); if (error != SQLITE_OK) { g_warning (sqlite3_errmsg (sqlite)); goto cleanup_mg_database_apply_single_migration; } error = sqlite3_step (statement); if (error != SQLITE_DONE) { g_warning (sqlite3_errmsg (sqlite)); goto cleanup_mg_database_apply_single_migration; } return_value = 1; cleanup_mg_database_apply_single_migration: if (statement) { sqlite3_finalize (statement); } return return_value; } const char * mg_database_get_error_string (MgDatabase *self) { sqlite3 *sqlite = mg_database_get_sqlite (self); return sqlite3_errmsg (sqlite); } static void mg_database_dispose (GObject *object) { MgDatabase *self = MG_DATABASE (object); sqlite3_close (self->sqlite); } MgDatabaseStatement * mg_database_prepare (MgDatabase *self, char *z_sql, const char **pz_tail) { sqlite3_stmt *statement = NULL; sqlite3 *sqlite = mg_database_get_sqlite (self); int error = sqlite3_prepare_v2 (sqlite, z_sql, -1, &statement, pz_tail); if (error != SQLITE_OK) { g_warning (sqlite3_errmsg (sqlite)); return NULL; } return mg_database_statement_new (self, statement); } int mg_database_get_affected_rows (MgDatabase *self) { sqlite3 *sqlite = mg_database_get_sqlite (self); return sqlite3_changes (sqlite); }