Replace the Purple.Sqlite3 API with Seagull

Fri, 07 Feb 2025 00:14:03 -0600

author
Gary Kramlich <grim@reaperworld.com>
date
Fri, 07 Feb 2025 00:14:03 -0600
changeset 43175
41ad34b9de13
parent 43174
94bf15b8dad0
child 43176
04f562dc0ff2

Replace the Purple.Sqlite3 API with Seagull

Seagull is our new SQLite3 utility library which will be expanded in the near
future.

Testing Done:
Ran in the devenv and built and ran the flatpak as well.

Reviewed at https://reviews.imfreedom.org/r/3821/

im.pidgin.Pidgin3.yml file | annotate | diff | comparison | revisions
libpurple/meson.build file | annotate | diff | comparison | revisions
libpurple/purplesqlite3.c file | annotate | diff | comparison | revisions
libpurple/purplesqlite3.h file | annotate | diff | comparison | revisions
libpurple/purplesqlitehistoryadapter.c file | annotate | diff | comparison | revisions
libpurple/tests/meson.build file | annotate | diff | comparison | revisions
libpurple/tests/sqlite3/initial.sql file | annotate | diff | comparison | revisions
libpurple/tests/sqlite3/malformed.sql file | annotate | diff | comparison | revisions
libpurple/tests/sqlite3/meson.build file | annotate | diff | comparison | revisions
libpurple/tests/sqlite3/secondary.sql file | annotate | diff | comparison | revisions
libpurple/tests/sqlite3/test_sqlite3.c file | annotate | diff | comparison | revisions
libpurple/tests/sqlite3/test_sqlite3.gresource.xml file | annotate | diff | comparison | revisions
meson.build file | annotate | diff | comparison | revisions
po/POTFILES.in file | annotate | diff | comparison | revisions
subprojects/seagull.wrap file | annotate | diff | comparison | revisions
--- a/im.pidgin.Pidgin3.yml	Thu Feb 06 20:29:19 2025 -0600
+++ b/im.pidgin.Pidgin3.yml	Fri Feb 07 00:14:03 2025 -0600
@@ -95,6 +95,15 @@
         archive-type: tar-xz
         url: https://sourceforge.net/projects/pidgin/files/gplugin/0.44.2/gplugin-0.44.2.tar.xz/download
         sha256: aea244e1add9628b50ec042c54cf93803f4577f8f142678f09b91fd4c0b20f72
+  - name: seagull
+    buildsystem: meson
+    config-opts:
+      - "--wrap-mode=nofallback"
+    sources:
+      - type: archive
+        archive-type: tar-xz
+        url: https://sourceforge.net/projects/pidgin/files/seagull/0.1.1/seagull-0.1.1.tar.xz
+        sha256: 499fc45b6a8539bc2293b362fec1c847fe25697355a0572f07ace3f359a38873
   - name: pidgin3
     buildsystem: meson
     config-opts:
--- a/libpurple/meson.build	Thu Feb 06 20:29:19 2025 -0600
+++ b/libpurple/meson.build	Fri Feb 07 00:14:03 2025 -0600
@@ -70,7 +70,6 @@
 	'purpleprotocolwhiteboard.c',
 	'purpleproxyinfo.c',
 	'purplesavedpresence.c',
-	'purplesqlite3.c',
 	'purplesqlitehistoryadapter.c',
 	'purpletags.c',
 	'purpleui.c',
@@ -166,7 +165,6 @@
 	'purpleprotocolwhiteboard.h',
 	'purpleproxyinfo.h',
 	'purplesavedpresence.h',
-	'purplesqlite3.h',
 	'purplesqlitehistoryadapter.h',
 	'purpletags.h',
 	'purpletyping.h',
@@ -312,7 +310,7 @@
                     dependencies : # static_link_libs
                         [dnsapi, ws2_32, glib, gio, gplugin_dep, libsoup,
                          libxml, gdk_pixbuf, gstreamer, gstreamer_app, json,
-                         sqlite3, math, birb_dep])
+                         seagull_dep, sqlite3, math, birb_dep])
 
 install_headers(purple_coreheaders,
                 subdir : purple_include_base)
--- a/libpurple/purplesqlite3.c	Thu Feb 06 20:29:19 2025 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,211 +0,0 @@
-/*
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This library 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 2 of the License, or (at your option)
- * any later version.
- *
- * This library 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
- * this library; if not, see <https://www.gnu.org/licenses/>.
- */
-
-#include <gio/gio.h>
-
-#include <sqlite3.h>
-
-#include "purplesqlite3.h"
-
-/******************************************************************************
- * Helpers
- *****************************************************************************/
-static gboolean
-purple_sqlite3_run_migration(sqlite3 *db, int version, const char *migration,
-                             GError **error)
-{
-	char *errmsg = NULL;
-	char *str = NULL;
-	gboolean success = TRUE;
-
-	str = g_strdup_printf("BEGIN;%s;PRAGMA user_version=%d;COMMIT;", migration,
-	                      version);
-
-	sqlite3_exec(db, str, NULL, NULL, &errmsg);
-	if(errmsg != NULL) {
-		g_set_error(error, PURPLE_SQLITE3_DOMAIN, 0,
-		            "failed to run migration: %s", errmsg);
-
-		sqlite3_free(errmsg);
-
-		sqlite3_exec(db, "ROLLBACK", NULL, NULL, &errmsg);
-		if(errmsg != NULL) {
-			g_error("failed to rollback transaction: %s", errmsg);
-
-			sqlite3_free(errmsg);
-		}
-
-		success = FALSE;
-	}
-
-	g_free(str);
-
-	return success;
-}
-
-/******************************************************************************
- * Public API
- *****************************************************************************/
-int
-purple_sqlite3_get_schema_version(sqlite3 *db, GError **error) {
-	sqlite3_stmt *stmt = NULL;
-	int version = -1;
-
-	g_return_val_if_fail(db != NULL, -1);
-
-	sqlite3_prepare_v2(db, "PRAGMA user_version", -1, &stmt, NULL);
-
-	if(stmt == NULL) {
-		g_set_error(error, PURPLE_SQLITE3_DOMAIN, 0,
-		            "error while creating prepared statement: %s",
-		            sqlite3_errmsg(db));
-
-		return -1;
-	}
-
-	if(sqlite3_step(stmt) == SQLITE_ROW) {
-		version = sqlite3_column_int(stmt, 0);
-	} else {
-		g_set_error_literal(error, PURPLE_SQLITE3_DOMAIN, 0,
-		                    "'PRAGMA user_version' didn't return a row");
-
-		sqlite3_finalize(stmt);
-
-		return -1;
-	}
-
-	sqlite3_finalize(stmt);
-
-	return version;
-}
-
-gboolean
-purple_sqlite3_run_migrations_from_strings(sqlite3 *db,
-                                           const char *migrations[],
-                                           GError **error)
-{
-	int current_version = 0;
-	guint n_migrations = 0;
-
-	g_return_val_if_fail(db != NULL, FALSE);
-	g_return_val_if_fail(migrations != NULL, FALSE);
-
-	/* Get the current version or bail if it failed. */
-	current_version = purple_sqlite3_get_schema_version(db, error);
-	if(current_version == -1) {
-		return FALSE;
-	}
-
-	n_migrations = g_strv_length((char **)migrations);
-	if((guint)current_version > n_migrations) {
-		g_set_error(error, PURPLE_SQLITE3_DOMAIN, 0,
-		            "schema version %u is higher than known migrations %u",
-		            (guint)current_version, n_migrations);
-
-		return FALSE;
-	}
-
-	for(int i = current_version; migrations[i] != NULL; i++) {
-		int version = i + 1;
-
-		if(!purple_sqlite3_run_migration(db, version, migrations[i], error)) {
-			return FALSE;
-		}
-	}
-
-	return TRUE;
-}
-
-gboolean
-purple_sqlite3_run_migrations_from_resources(sqlite3 *db, const char *path,
-                                             const char *migrations[],
-                                             GError **error)
-{
-	GError *local_error = NULL;
-	int current_version = 0;
-	guint n_migrations = 0;
-
-	g_return_val_if_fail(db != NULL, FALSE);
-	g_return_val_if_fail(path != NULL, FALSE);
-	g_return_val_if_fail(migrations != NULL, FALSE);
-
-	/* Get the current version or bail if it failed. */
-	current_version = purple_sqlite3_get_schema_version(db, error);
-	if(current_version == -1) {
-		return FALSE;
-	}
-
-	n_migrations = g_strv_length((char **)migrations);
-	if((guint)current_version > n_migrations) {
-		g_set_error(error, PURPLE_SQLITE3_DOMAIN, 0,
-		            "schema version %u is higher than known migrations %u",
-		            (guint)current_version, n_migrations);
-
-		return FALSE;
-	}
-
-	/* `PRAGMA user_version` starts at 0, so write our version as i + 1. We
-	 * start iterating the list of migrations at the current version of the
-	 * database. If the database is already up to date, then current_version
-	 * will point us at the null terminator in the list of migrations, which
-	 * will short circuit the for loop.
-	 */
-	for(int i = current_version; migrations[i] != NULL; i++) {
-		GBytes *data = NULL;
-		char *full_path = NULL;
-		const gchar *migration = NULL;
-		int version = i + 1;
-
-		/* Get the data from the resource */
-		full_path = g_build_path("/", path, migrations[i], NULL);
-
-		data = g_resources_lookup_data(full_path, G_RESOURCE_LOOKUP_FLAGS_NONE,
-		                               &local_error);
-		if(data == NULL || local_error != NULL) {
-			if(local_error == NULL) {
-				local_error = g_error_new(PURPLE_SQLITE3_DOMAIN, 0,
-				                          "failed to load resource %s",
-				                          full_path);
-			}
-
-			g_propagate_error(error, local_error);
-
-			g_clear_pointer(&data, g_bytes_unref);
-			g_free(full_path);
-
-			return FALSE;
-		}
-
-		g_free(full_path);
-
-		migration = (const char *)g_bytes_get_data(data, NULL);
-		if(!purple_sqlite3_run_migration(db, version, migration, error)) {
-			g_bytes_unref(data);
-
-			return FALSE;
-		}
-
-		g_bytes_unref(data);
-	}
-
-	return TRUE;
-}
--- a/libpurple/purplesqlite3.h	Thu Feb 06 20:29:19 2025 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,154 +0,0 @@
-/*
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This library 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 2 of the License, or (at your option)
- * any later version.
- *
- * This library 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
- * this library; if not, see <https://www.gnu.org/licenses/>.
- */
-
-#if !defined(PURPLE_GLOBAL_HEADER_INSIDE) && !defined(PURPLE_COMPILATION)
-# error "only <purple.h> may be included directly"
-#endif
-
-#ifndef PURPLE_SQLITE3_H
-#define PURPLE_SQLITE3_H
-
-#include <glib.h>
-#include <gio/gio.h>
-
-#include "purpleversion.h"
-
-G_BEGIN_DECLS
-
-/**
- * PURPLE_SQLITE3_ERROR:
- *
- * An error domain for sqlite3 errors.
- *
- * Since: 3.0
- */
-#define PURPLE_SQLITE3_DOMAIN \
-	g_quark_from_static_string("sqlite3") \
-	PURPLE_AVAILABLE_MACRO_IN_3_0
-
-/**
- * PurpleSqlite3:
- *
- * A sqlite3 connection.
- *
- * This type alias exists to avoid requiring the sqlite3 header, but is no
- * different from the `sqlite3` type.
- *
- * If that header is included first, this will be exactly equivalent, otherwise
- * it will be a generic `gpointer`, which you should cast to a `sqlite3`.
- *
- * Since: 3.0
- */
-PURPLE_AVAILABLE_TYPE_IN_3_0
-#ifdef SQLITE_API
-typedef sqlite3 PurpleSqlite3;
-#else
-typedef gpointer PurpleSqlite3;
-#endif
-
-/**
- * purple_sqlite3_get_schema_version:
- * @db: The sqlite3 connection.
- * @error: Return address for a #GError, or %NULL.
- *
- * Attempts to read the result of `PRAGMA user_version` which this API uses to
- * store the schema version.
- *
- * Returns: %TRUE on success, or %FALSE on error with @error set.
- *
- * Since: 3.0
- */
-PURPLE_AVAILABLE_IN_3_0
-int purple_sqlite3_get_schema_version(PurpleSqlite3 *db, GError **error);
-
-/**
- * purple_sqlite3_run_migrations_from_strings:
- * @db: The sqlite3 connection.
- * @migrations: (array zero-terminated=1): A list of SQL statements, each item
- *              being its own migration.
- * @error: Return address for a #GError, or %NULL.
- *
- * Runs the given migrations in the order they are given. The index of each
- * migration plus 1 is assumed is to be the version number of the migration,
- * which means that you can not change the order of the migrations. The
- * reasoning for the addition of 1 is because `PRAGMA user_version` defaults to
- * 0.
- *
- * This expects each string in @migrations to be a complete migration. That is,
- * each string in the array should contain all of the SQL for that migration.
- * For example, if you're expecting to have 2 migrations, the initial creating
- * two tables, and then adding a column to one of the existing tables, you
- * would have something like the following code.
- *
- * ```c
- * const char *migrations[] = {
- *     // Our initial migration that creates user and session tables.
- *     "CREATE TABLE user(id INTEGER PRIMARY KEY, name TEXT);"
- *     "CREATE TABLE session(user INTEGER, token TEXT) FOREIGN KEY(user) REFERENCES user(id);",
- *     // Begin our second migration that will add a display name to the user
- *     // table. Note the ',' at the end of the previous line.
- *     "ALTER TABLE user ADD COLUMN(display_name TEXT);",
- *     NULL
- * };
- * ```
- *
- * Also, this function will run each migration in its own transaction so you
- * don't need to worry about them. This is done to make sure that the database
- * stays at a known version and an incomplete migration will not be saved.
- *
- * Returns: %TRUE on success, or %FALSE on error potentially with @error set.
- *
- * Since: 3.0
- */
-PURPLE_AVAILABLE_IN_3_0
-gboolean purple_sqlite3_run_migrations_from_strings(PurpleSqlite3 *db, const char *migrations[], GError **error);
-
-/**
- * purple_sqlite3_run_migrations_from_resources:
- * @db: The sqlite3 connection.
- * @path: The base path in @resource to use.
- * @migrations: (array zero-terminated=1): The list of migrations in the order
- *              to run them.
- * @error: Return address for a #GError, or %NULL.
- *
- * Runs the given migrations in the order they are given. The index of each
- * migration plus 1 is assumed to be the version number of the migration, which
- * means that you can not change the order of the migrations. The reasoning for
- * the addition of 1 is because `PRAGMA user_version` defaults to 0.
- *
- * This will attempt to load the migrations via
- * [func@Gio.resources_open_stream] by concatenating @path and the individual
- * items of @migrations. Each migration will be ran in a transaction that
- * includes updating the schema version, which is stored in
- * `PRAGMA user_version`. This means you can't use `PRAGMA user_version` for
- * other things.
- *
- * Returns: %TRUE on success, or %FALSE on error potentially with @error set.
- *
- * Since: 3.0
- */
-PURPLE_AVAILABLE_IN_3_0
-gboolean purple_sqlite3_run_migrations_from_resources(PurpleSqlite3 *db, const char *path, const char *migrations[], GError **error);
-
-G_END_DECLS
-
-#endif /* PURPLE_SQLITE3_H */
--- a/libpurple/purplesqlitehistoryadapter.c	Thu Feb 06 20:29:19 2025 -0600
+++ b/libpurple/purplesqlitehistoryadapter.c	Fri Feb 07 00:14:03 2025 -0600
@@ -23,6 +23,7 @@
 #include <glib/gi18n-lib.h>
 
 #include <sqlite3.h>
+#include <seagull.h>
 
 #include "purplesqlitehistoryadapter.h"
 
@@ -31,7 +32,6 @@
 #include "purpleconversationmanager.h"
 #include "purpleconversationmember.h"
 #include "purpleconversationmembers.h"
-#include "purplesqlite3.h"
 
 struct _PurpleSqliteHistoryAdapter {
 	PurpleHistoryAdapter parent;
@@ -72,8 +72,8 @@
 		NULL
 	};
 
-	return purple_sqlite3_run_migrations_from_resources(adapter->db, path,
-	                                                    migrations, error);
+	return seagull_migrations_run_from_resources(adapter->db, path, migrations,
+	                                             error);
 }
 
 static sqlite3_stmt *
--- a/libpurple/tests/meson.build	Thu Feb 06 20:29:19 2025 -0600
+++ b/libpurple/tests/meson.build	Fri Feb 07 00:14:03 2025 -0600
@@ -90,4 +90,3 @@
 endforeach
 
 subdir('avatar')
-subdir('sqlite3')
--- a/libpurple/tests/sqlite3/initial.sql	Thu Feb 06 20:29:19 2025 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-CREATE TABLE foo(a TEXT);
-CREATE TABLE bar(b TEXT);
\ No newline at end of file
--- a/libpurple/tests/sqlite3/malformed.sql	Thu Feb 06 20:29:19 2025 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-CREATE TABLE foo(a TEXT;
\ No newline at end of file
--- a/libpurple/tests/sqlite3/meson.build	Thu Feb 06 20:29:19 2025 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-TEST_SQLITE3_SOURCES = [
-	'test_sqlite3.c'
-]
-
-TEST_SQLITE3_RESOURCES = gnome.compile_resources('test_sqlite3_resources',
-	'test_sqlite3.gresource.xml',
-	source_dir : '.',
-	c_name : 'test_sqlite3')
-TEST_SQLITE3_SOURCES += TEST_SQLITE3_RESOURCES
-
-test_sqlite3 = executable(
-	'test_sqlite3',
-	TEST_SQLITE3_SOURCES,
-	dependencies : [libpurple_dep, glib, sqlite3])
-
-test('sqlite3', test_sqlite3)
--- a/libpurple/tests/sqlite3/secondary.sql	Thu Feb 06 20:29:19 2025 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-CREATE TABLE baz(c TEXT);
\ No newline at end of file
--- a/libpurple/tests/sqlite3/test_sqlite3.c	Thu Feb 06 20:29:19 2025 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,477 +0,0 @@
-/*
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, see <https://www.gnu.org/licenses/>.
- */
-
-#include <glib.h>
-
-#include <sqlite3.h>
-
-#include <purple.h>
-
-/******************************************************************************
- * get schema version tests
- *****************************************************************************/
-static void
-test_sqlite3_get_schema_version_null(void) {
-	if(g_test_subprocess()) {
-		GError *error = NULL;
-		int version = 0;
-
-		version = purple_sqlite3_get_schema_version(NULL, &error);
-		g_assert_error(error, PURPLE_SQLITE3_DOMAIN, 0);
-		g_clear_error(&error);
-		g_assert_cmpint(version, ==, -1);
-	}
-
-	g_test_trap_subprocess(NULL, 0, 0);
-	g_test_trap_assert_failed();
-	g_test_trap_assert_stderr("*assertion*!= NULL*");
-}
-
-static void
-test_sqlite3_get_schema_version_new(void) {
-	GError *error = NULL;
-	sqlite3 *db = NULL;
-	int rc = 0;
-	int version = 0;
-
-	rc = sqlite3_open(":memory:", &db);
-	g_assert_nonnull(db);
-	g_assert_cmpint(rc, ==, SQLITE_OK);
-
-	version = purple_sqlite3_get_schema_version(db, &error);
-	g_assert_no_error(error);
-	g_assert_cmpint(version, ==, 0);
-
-	rc = sqlite3_close(db);
-	g_assert_cmpint(rc, ==, SQLITE_OK);
-}
-
-/******************************************************************************
- * string migration tests
- *****************************************************************************/
-static void
-test_sqlite3_string_migrations_null(void) {
-	if(g_test_subprocess()) {
-		GError *error = NULL;
-		sqlite3 *db = NULL;
-		gboolean res = FALSE;
-		int rc = 0;
-
-		rc = sqlite3_open(":memory:", &db);
-		g_assert_nonnull(db);
-		g_assert_cmpint(rc, ==, SQLITE_OK);
-
-		res = purple_sqlite3_run_migrations_from_strings(db, NULL, &error);
-		g_assert_no_error(error);
-		g_assert_false(res);
-	}
-
-	g_test_trap_subprocess(NULL, 0, 0);
-	g_test_trap_assert_failed();
-	g_test_trap_assert_stderr("*migrations != NULL*");
-}
-
-static void
-test_sqlite3_string_migrations_null_terminator(void) {
-	GError *error = NULL;
-	sqlite3 *db = NULL;
-	gboolean res = FALSE;
-	int rc = 0;
-	int version = -1;
-	const char *migrations[] = {NULL};
-
-	rc = sqlite3_open(":memory:", &db);
-	g_assert_nonnull(db);
-	g_assert_cmpint(rc, ==, SQLITE_OK);
-
-	res = purple_sqlite3_run_migrations_from_strings(db, migrations, &error);
-	g_assert_no_error(error);
-	g_assert_true(res);
-
-	version = purple_sqlite3_get_schema_version(db, &error);
-	g_assert_no_error(error);
-	g_assert_cmpint(version, ==, 0);
-
-	rc = sqlite3_close(db);
-	g_assert_cmpint(rc, ==, SQLITE_OK);
-}
-
-static void
-test_sqlite3_string_migrations_multiple(void) {
-	GError *error = NULL;
-	sqlite3 *db = NULL;
-	gboolean res = FALSE;
-	int rc = 0;
-	int version = -1;
-	const char *migrations[] = {
-		"CREATE TABLE foo(a TEXT); CREATE TABLE bar(b TEXT);",
-		"CREATE TABLE baz(c TEXT);",
-		NULL
-	};
-
-	rc = sqlite3_open(":memory:", &db);
-	g_assert_nonnull(db);
-	g_assert_cmpint(rc, ==, SQLITE_OK);
-
-	res = purple_sqlite3_run_migrations_from_strings(db, migrations, &error);
-	g_assert_no_error(error);
-	g_assert_true(res);
-
-	version = purple_sqlite3_get_schema_version(db, &error);
-	g_assert_no_error(error);
-	g_assert_cmpint(version, ==, 2);
-
-	/* Run the migrations again and make sure we remain at schema version 2. */
-	res = purple_sqlite3_run_migrations_from_strings(db, migrations, &error);
-	g_assert_no_error(error);
-	g_assert_true(res);
-
-	version = purple_sqlite3_get_schema_version(db, &error);
-	g_assert_no_error(error);
-	g_assert_cmpint(version, ==, 2);
-
-	rc = sqlite3_close(db);
-	g_assert_cmpint(rc, ==, SQLITE_OK);
-}
-
-static void
-test_sqlite3_string_migrations_syntax_error(void) {
-	GError *error = NULL;
-	sqlite3 *db = NULL;
-	gboolean res = FALSE;
-	int rc = 0;
-	int version = -1;
-	const char *migrations[] = {
-		"CREATE TABLE broke(a TEXT",
-		NULL
-	};
-
-	rc = sqlite3_open(":memory:", &db);
-	g_assert_nonnull(db);
-	g_assert_cmpint(rc, ==, SQLITE_OK);
-
-	res = purple_sqlite3_run_migrations_from_strings(db, migrations, &error);
-	g_assert_error(error, PURPLE_SQLITE3_DOMAIN, 0);
-	g_clear_error(&error);
-	g_assert_false(res);
-
-	version = purple_sqlite3_get_schema_version(db, &error);
-	g_assert_no_error(error);
-	g_assert_cmpint(version, ==, 0);
-
-	rc = sqlite3_close(db);
-	g_assert_cmpint(rc, ==, SQLITE_OK);
-}
-
-static void
-test_sqlite3_string_migrations_older(void) {
-	GError *error = NULL;
-	sqlite3 *db = NULL;
-	gboolean res = FALSE;
-	int rc = 0;
-	int version = -1;
-	const char *migrations1[] = {
-		"CREATE TABLE foo(a TEXT); CREATE TABLE bar(b TEXT);",
-		"CREATE TABLE baz(c TEXT);",
-		NULL
-	};
-	const char *migrations2[] = {
-		"CREATE TABLE foo(a TEXT); CREATE TABLE bar(b TEXT);",
-		NULL
-	};
-
-	rc = sqlite3_open(":memory:", &db);
-	g_assert_nonnull(db);
-	g_assert_cmpint(rc, ==, SQLITE_OK);
-
-	res = purple_sqlite3_run_migrations_from_strings(db, migrations1, &error);
-	g_assert_no_error(error);
-	g_assert_true(res);
-
-	version = purple_sqlite3_get_schema_version(db, &error);
-	g_assert_no_error(error);
-	g_assert_cmpint(version, ==, 2);
-
-	/* Run the older migrations now and verify we get a failure. */
-	res = purple_sqlite3_run_migrations_from_strings(db, migrations2, &error);
-	g_assert_error(error, PURPLE_SQLITE3_DOMAIN, 0);
-	g_clear_error(&error);
-	g_assert_false(res);
-
-	version = purple_sqlite3_get_schema_version(db, &error);
-	g_assert_no_error(error);
-	g_assert_cmpint(version, ==, 2);
-
-	rc = sqlite3_close(db);
-	g_assert_cmpint(rc, ==, SQLITE_OK);
-}
-
-/******************************************************************************
- * resource migration tests
- *****************************************************************************/
-static void
-test_sqlite3_resource_migrations_null_path(void) {
-	if(g_test_subprocess()) {
-		GError *error = NULL;
-		sqlite3 *db = NULL;
-		gboolean res = FALSE;
-		int rc = 0;
-
-		rc = sqlite3_open(":memory:", &db);
-		g_assert_nonnull(db);
-		g_assert_cmpint(rc, ==, SQLITE_OK);
-
-		res = purple_sqlite3_run_migrations_from_resources(db, NULL, NULL,
-		                                                   &error);
-		g_assert_no_error(error);
-		g_assert_false(res);
-
-		rc = sqlite3_close(db);
-		g_assert_cmpint(rc, ==, SQLITE_OK);
-	}
-
-	g_test_trap_subprocess(NULL, 0, 0);
-	g_test_trap_assert_failed();
-	g_test_trap_assert_stderr("*path != NULL*");
-}
-
-static void
-test_sqlite3_resource_migrations_null_migrations(void) {
-	if(g_test_subprocess()) {
-		GError *error = NULL;
-		sqlite3 *db = NULL;
-		gboolean res = FALSE;
-		const char *path = "/im/libpidgin/purple/tests/sqlite3/";
-		int rc = 0;
-
-		rc = sqlite3_open(":memory:", &db);
-		g_assert_nonnull(db);
-		g_assert_cmpint(rc, ==, SQLITE_OK);
-
-		res = purple_sqlite3_run_migrations_from_resources(db, path, NULL,
-		                                                   &error);
-		g_assert_no_error(error);
-		g_assert_false(res);
-
-		rc = sqlite3_close(db);
-		g_assert_cmpint(rc, ==, SQLITE_OK);
-	}
-
-	g_test_trap_subprocess(NULL, 0, 0);
-	g_test_trap_assert_failed();
-	g_test_trap_assert_stderr("*migrations != NULL*");
-}
-
-static void
-test_sqlite3_resource_migrations_null_terminator(void) {
-	GError *error = NULL;
-	sqlite3 *db = NULL;
-	gboolean res = FALSE;
-	int rc = 0;
-	int version = -1;
-	const char *migrations[] = {NULL};
-	const char *path = "/im/pidgin/libpurple/tests/sqlite3/";
-
-	rc = sqlite3_open(":memory:", &db);
-	g_assert_nonnull(db);
-	g_assert_cmpint(rc, ==, SQLITE_OK);
-
-	res = purple_sqlite3_run_migrations_from_resources(db, path, migrations,
-	                                                   &error);
-	g_assert_no_error(error);
-	g_assert_true(res);
-
-	version = purple_sqlite3_get_schema_version(db, &error);
-	g_assert_no_error(error);
-	g_assert_cmpint(version, ==, 0);
-
-	rc = sqlite3_close(db);
-	g_assert_cmpint(rc, ==, SQLITE_OK);
-}
-
-static void
-test_sqlite3_resource_migrations_multiple(void) {
-	GError *error = NULL;
-	sqlite3 *db = NULL;
-	gboolean res = FALSE;
-	int rc = 0;
-	int version = -1;
-	const char *migrations[] = {"initial.sql", "secondary.sql", NULL};
-	const char *path = "/im/pidgin/libpurple/tests/sqlite3/";
-
-	rc = sqlite3_open(":memory:", &db);
-	g_assert_nonnull(db);
-	g_assert_cmpint(rc, ==, SQLITE_OK);
-
-	res = purple_sqlite3_run_migrations_from_resources(db, path, migrations,
-	                                                   &error);
-	g_assert_no_error(error);
-	g_assert_true(res);
-
-	version = purple_sqlite3_get_schema_version(db, &error);
-	g_assert_no_error(error);
-	g_assert_cmpint(version, ==, 2);
-
-	/* Run the migrations again and make sure we remain at schema version 2. */
-	res = purple_sqlite3_run_migrations_from_strings(db, migrations, &error);
-	g_assert_no_error(error);
-	g_assert_true(res);
-
-	version = purple_sqlite3_get_schema_version(db, &error);
-	g_assert_no_error(error);
-	g_assert_cmpint(version, ==, 2);
-
-	rc = sqlite3_close(db);
-	g_assert_cmpint(rc, ==, SQLITE_OK);
-}
-
-static void
-test_sqlite3_resource_migrations_missing(void) {
-	GError *error = NULL;
-	sqlite3 *db = NULL;
-	gboolean res = FALSE;
-	int rc = 0;
-	int version = -1;
-	const char *migrations[] = {"initial.sql", "imaginary.sql", NULL};
-	const char *path = "/im/pidgin/libpurple/tests/sqlite3/";
-
-	rc = sqlite3_open(":memory:", &db);
-	g_assert_nonnull(db);
-	g_assert_cmpint(rc, ==, SQLITE_OK);
-
-	res = purple_sqlite3_run_migrations_from_resources(db, path, migrations,
-	                                                   &error);
-	g_assert_error(error, G_RESOURCE_ERROR, 0);
-	g_clear_error(&error);
-	g_assert_false(res);
-
-	version = purple_sqlite3_get_schema_version(db, &error);
-	g_assert_no_error(error);
-	g_assert_cmpint(version, ==, 1);
-
-	rc = sqlite3_close(db);
-	g_assert_cmpint(rc, ==, SQLITE_OK);
-}
-
-static void
-test_sqlite3_resource_migrations_syntax_error(void) {
-	GError *error = NULL;
-	sqlite3 *db = NULL;
-	gboolean res = FALSE;
-	int rc = 0;
-	int version = -1;
-	const char *migrations[] = {"malformed.sql", NULL};
-	const char *path = "/im/pidgin/libpurple/tests/sqlite3/";
-
-	rc = sqlite3_open(":memory:", &db);
-	g_assert_nonnull(db);
-	g_assert_cmpint(rc, ==, SQLITE_OK);
-
-	res = purple_sqlite3_run_migrations_from_resources(db, path, migrations,
-	                                                   &error);
-	g_assert_error(error, PURPLE_SQLITE3_DOMAIN, 0);
-	g_clear_error(&error);
-	g_assert_false(res);
-
-	version = purple_sqlite3_get_schema_version(db, &error);
-	g_assert_no_error(error);
-	g_assert_cmpint(version, ==, 0);
-
-	rc = sqlite3_close(db);
-	g_assert_cmpint(rc, ==, SQLITE_OK);
-}
-
-static void
-test_sqlite3_resource_migrations_older(void) {
-	GError *error = NULL;
-	sqlite3 *db = NULL;
-	gboolean res = FALSE;
-	int rc = 0;
-	int version = -1;
-	const char *migrations1[] = {"initial.sql", "secondary.sql", NULL};
-	const char *migrations2[] = {"initial.sql", NULL};
-	const char *path = "/im/pidgin/libpurple/tests/sqlite3/";
-
-	rc = sqlite3_open(":memory:", &db);
-	g_assert_nonnull(db);
-	g_assert_cmpint(rc, ==, SQLITE_OK);
-
-	res = purple_sqlite3_run_migrations_from_resources(db, path, migrations1,
-	                                                   &error);
-	g_assert_no_error(error);
-	g_assert_true(res);
-
-	version = purple_sqlite3_get_schema_version(db, &error);
-	g_assert_no_error(error);
-	g_assert_cmpint(version, ==, 2);
-
-	/* Run the older migrations now and verify we get a failure. */
-	res = purple_sqlite3_run_migrations_from_resources(db, path, migrations2,
-	                                                   &error);
-	g_assert_error(error, PURPLE_SQLITE3_DOMAIN, 0);
-	g_clear_error(&error);
-	g_assert_false(res);
-
-	version = purple_sqlite3_get_schema_version(db, &error);
-	g_assert_no_error(error);
-	g_assert_cmpint(version, ==, 2);
-
-	rc = sqlite3_close(db);
-	g_assert_cmpint(rc, ==, SQLITE_OK);
-}
-
-/******************************************************************************
- * Main
- *****************************************************************************/
-int
-main(int argc, char *argv[]) {
-	g_test_init(&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL);
-
-	g_test_add_func("/sqlite3/schema_version/null",
-	                test_sqlite3_get_schema_version_null);
-	g_test_add_func("/sqlite3/schema_version/new",
-	                test_sqlite3_get_schema_version_new);
-
-	g_test_add_func("/sqlite3/string_migrations/null",
-	                test_sqlite3_string_migrations_null);
-	g_test_add_func("/sqlite3/string_migrations/null-terminator",
-	                test_sqlite3_string_migrations_null_terminator);
-	g_test_add_func("/sqlite3/string_migrations/multiple",
-	                test_sqlite3_string_migrations_multiple);
-	g_test_add_func("/sqlite3/string_migrations/syntax-error",
-	                test_sqlite3_string_migrations_syntax_error);
-	g_test_add_func("/sqlite3/string_migrations/older",
-	                test_sqlite3_string_migrations_older);
-
-	g_test_add_func("/sqlite3/resource_migrations/null-path",
-	                test_sqlite3_resource_migrations_null_path);
-	g_test_add_func("/sqlite3/resource_migrations/null-migrations",
-	                test_sqlite3_resource_migrations_null_migrations);
-	g_test_add_func("/sqlite3/resource_migrations/null-terminator",
-	                test_sqlite3_resource_migrations_null_terminator);
-	g_test_add_func("/sqlite3/resource_migrations/multiple",
-	                test_sqlite3_resource_migrations_multiple);
-	g_test_add_func("/sqlite3/resource_migrations/missing",
-	                test_sqlite3_resource_migrations_missing);
-	g_test_add_func("/sqlite3/resource_migrations/syntax-error",
-	                test_sqlite3_resource_migrations_syntax_error);
-	g_test_add_func("/sqlite3/resource_migrations/older",
-	                test_sqlite3_resource_migrations_older);
-
-	return g_test_run();
-}
--- a/libpurple/tests/sqlite3/test_sqlite3.gresource.xml	Thu Feb 06 20:29:19 2025 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<gresources>
-  <gresource prefix="/im/pidgin/libpurple/tests/sqlite3/">
-    <file compressed="true">initial.sql</file>
-    <file compressed="true">secondary.sql</file>
-    <file compressed="true">malformed.sql</file>
-  </gresource>
-</gresources>
--- a/meson.build	Thu Feb 06 20:29:19 2025 -0600
+++ b/meson.build	Fri Feb 07 00:14:03 2025 -0600
@@ -181,11 +181,6 @@
 	language : 'c',)
 
 #######################################################################
-# Check for gdk-pixbuf (required)
-#######################################################################
-gdk_pixbuf = dependency('gdk-pixbuf-2.0', version : '>= 2.26.0')
-
-#######################################################################
 # Check for GObject Introspection
 #######################################################################
 if get_option('introspection')
@@ -209,53 +204,27 @@
 ENABLE_GTK = get_option('gtkui')
 
 #######################################################################
-# Check for LibXML2 (required)
-#######################################################################
-libxml = dependency('libxml-2.0', version : '>= 2.6.0')
-if libxml.version().version_compare('<2.6.18')
-	message('Versions of libxml2 < 2.6.18 may contain bugs that could cause XMPP messages to be discarded.')
-endif
-
+# Additional Dependencies
 #######################################################################
-# Check for JSON-GLib (required)
-#######################################################################
-
+birb_dep = dependency('birb', version : '>=0.3.1')
+gdk_pixbuf = dependency('gdk-pixbuf-2.0', version : '>= 2.26.0')
+gstreamer = dependency('gstreamer-1.0', version : '>=1.14')
+gstreamer_app = dependency('gstreamer-app-1.0')
 json = dependency('json-glib-1.0', version : '>= 0.14.0')
 
-######################################################################
-# Check for libsoup (required)
-#######################################################################
-
 libsoup = dependency('libsoup-3.0', version : '>= 3')
 add_project_arguments(
 	'-DSOUP_VERSION_MIN_REQUIRED=SOUP_VERSION_3_0',
 	'-DSOUP_VERSION_MAX_ALLOWED=SOUP_VERSION_3_0',
 	language : 'c')
 
-#######################################################################
-# Check for sqlite3 (required)
-#######################################################################
-sqlite3 = dependency('sqlite3', version : '>= 3.27.0')
-
-#######################################################################
-# Check for GStreamer
-#######################################################################
-
-gstreamer = dependency('gstreamer-1.0', version : '>=1.14')
+libxml = dependency('libxml-2.0', version : '>= 2.6.0')
+if libxml.version().version_compare('<2.6.18')
+	message('Versions of libxml2 < 2.6.18 may contain bugs that could cause XMPP messages to be discarded.')
+endif
 
-#######################################################################
-# Check for Raw data streams support in Farstream
-#######################################################################
-gstreamer_app = dependency('gstreamer-app-1.0')
-
-#######################################################################
-# Check for birb glib utility library
-#######################################################################
-birb_dep = dependency('birb', version : '>=0.3.1')
-
-#######################################################################
-# Check for Xeme XMPP Library
-#######################################################################
+seagull_dep = dependency('seagull', version : '>= 0.1.1')
+sqlite3 = dependency('sqlite3', version : '>= 3.27.0')
 xeme = dependency('xeme')
 
 dependency('shoes', required : false)
--- a/po/POTFILES.in	Thu Feb 06 20:29:19 2025 -0600
+++ b/po/POTFILES.in	Fri Feb 07 00:14:03 2025 -0600
@@ -91,7 +91,6 @@
 libpurple/purpleprotocolwhiteboard.c
 libpurple/purpleproxyinfo.c
 libpurple/purplesavedpresence.c
-libpurple/purplesqlite3.c
 libpurple/purplesqlitehistoryadapter.c
 libpurple/purpletags.c
 libpurple/purpleui.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/subprojects/seagull.wrap	Fri Feb 07 00:14:03 2025 -0600
@@ -0,0 +1,5 @@
+[wrap-file]
+directory = seagull-0.1.1
+source_url = https://sourceforge.net/projects/pidgin/files/seagull/0.1.1/seagull-0.1.1.tar.xz
+source_filename = seagull-0.1.1.tar.xz
+source_hash = 499fc45b6a8539bc2293b362fec1c847fe25697355a0572f07ace3f359a38873

mercurial