pidgin/pidgin

d78c0951ea2c
Parents c9b476c9c9d2
Children 657b634379ab
Create the PurpleTags object for handling tags

Testing Done:
Ran the unit tests.

Bugs closed: PIDGIN-17664

Reviewed at https://reviews.imfreedom.org/r/1777/
--- a/libpurple/meson.build Thu Sep 22 23:26:05 2022 -0500
+++ b/libpurple/meson.build Fri Sep 23 00:13:38 2022 -0500
@@ -83,6 +83,7 @@
'purpleproxyinfo.c',
'purpleroomlistroom.c',
'purplesqlitehistoryadapter.c',
+ 'purpletags.c',
'purpleuiinfo.c',
'purplewhiteboard.c',
'purplewhiteboardmanager.c',
@@ -185,6 +186,7 @@
'purpleproxyinfo.h',
'purpleroomlistroom.h',
'purplesqlitehistoryadapter.h',
+ 'purpletags.h',
'purpleuiinfo.h',
'purplewhiteboard.h',
'purplewhiteboardmanager.h',
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/purpletags.c Fri Sep 23 00:13:38 2022 -0500
@@ -0,0 +1,177 @@
+/*
+ * 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 program 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 program 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 program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "purpletags.h"
+
+#include "util.h"
+
+struct _PurpleTags {
+ GObject parent;
+
+ GList *tags;
+};
+
+G_DEFINE_TYPE(PurpleTags, purple_tags, G_TYPE_OBJECT)
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+
+static void
+purple_tags_dispose(GObject *obj) {
+ PurpleTags *tags = PURPLE_TAGS(obj);
+
+ if(tags->tags != NULL) {
+ g_list_free_full(tags->tags, g_free);
+ tags->tags = NULL;
+ }
+
+ G_OBJECT_CLASS(purple_tags_parent_class)->dispose(obj);
+}
+
+static void
+purple_tags_init(PurpleTags *tags) {
+}
+
+static void
+purple_tags_class_init(PurpleTagsClass *klass) {
+ GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+
+ obj_class->dispose = purple_tags_dispose;
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+PurpleTags *
+purple_tags_new(void) {
+ return g_object_new(PURPLE_TYPE_TAGS, NULL);
+}
+
+const gchar *
+purple_tags_lookup(PurpleTags *tags, const gchar *name, gboolean *found) {
+ size_t name_len = 0;
+
+ g_return_val_if_fail(PURPLE_IS_TAGS(tags), FALSE);
+ g_return_val_if_fail(name != NULL, FALSE);
+
+ /* Assume we're going to find the tag, if we don't we set found to false
+ * before we return. This sounds silly, but it saves some additional logic
+ * below.
+ */
+ if(found) {
+ *found = TRUE;
+ }
+
+ name_len = strlen(name);
+
+ for(GList *l = tags->tags; l != NULL; l = l->next) {
+ const gchar *tag = l->data;
+
+ if(g_str_has_prefix(tag, name)) {
+ const gchar *value = tag + name_len;
+
+ if(*value == '\0') {
+ return NULL;
+ } else if(*value == ':') {
+ return value+1;
+ }
+ }
+ }
+
+ /* We didn't find the tag, so set found to false if necessary. */
+ if(found) {
+ *found = FALSE;
+ }
+
+ return NULL;
+}
+
+const gchar *
+purple_tags_get(PurpleTags *tags, const gchar *name) {
+ g_return_val_if_fail(PURPLE_IS_TAGS(tags), NULL);
+ g_return_val_if_fail(name != NULL, NULL);
+
+ return purple_tags_lookup(tags, name, NULL);
+}
+
+void
+purple_tags_add(PurpleTags *tags, const gchar *tag) {
+ g_return_if_fail(PURPLE_IS_TAGS(tags));
+ g_return_if_fail(tag != NULL);
+
+ tags->tags = g_list_append(tags->tags, g_strdup(tag));
+}
+
+gboolean
+purple_tags_remove(PurpleTags *tags, const gchar *tag) {
+ g_return_val_if_fail(PURPLE_IS_TAGS(tags), FALSE);
+ g_return_val_if_fail(tag != NULL, FALSE);
+
+ for(GList *l = tags->tags; l != NULL; l = l->next) {
+ gchar *etag = l->data;
+
+ if(purple_strequal(etag, tag)) {
+ g_free(etag);
+ tags->tags = g_list_delete_link(tags->tags, l);
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+guint
+purple_tags_get_count(PurpleTags *tags) {
+ g_return_val_if_fail(PURPLE_IS_TAGS(tags), 0);
+
+ return g_list_length(tags->tags);
+}
+
+GList *
+purple_tags_get_all(PurpleTags *tags) {
+ g_return_val_if_fail(PURPLE_IS_TAGS(tags), NULL);
+
+ return tags->tags;
+}
+
+gchar *
+purple_tags_to_string(PurpleTags *tags, const gchar *separator) {
+ GString *value = NULL;
+
+ g_return_val_if_fail(PURPLE_IS_TAGS(tags), NULL);
+
+ value = g_string_new("");
+
+ for(GList *l = tags->tags; l != NULL; l = l->next) {
+ const gchar *tag = l->data;
+
+ g_string_append(value, tag);
+
+ if(separator != NULL && l->next != NULL) {
+ g_string_append(value, separator);
+ }
+ }
+
+ return g_string_free(value, FALSE);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/purpletags.h Fri Sep 23 00:13:38 2022 -0500
@@ -0,0 +1,157 @@
+/*
+ * 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 program 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 program 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 program; 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_TAGS_H
+#define PURPLE_TAGS_H
+
+#include <glib.h>
+#include <gio/gio.h>
+
+#define PURPLE_TYPE_TAGS (purple_tags_get_type())
+G_DECLARE_FINAL_TYPE(PurpleTags, purple_tags, PURPLE, TAGS, GObject)
+
+/**
+ * PurpleTags:
+ *
+ * Tags is an object that can be used to keep track of arbitrary tags on
+ * objects. Tags are simple strings that use the first ':' to delimit a value.
+ * For example: `foo` is a tag with just a name and no value, but `foo:bar` is
+ * a tag with a name and value. Please note this distinction when the API calls
+ * for a name versus a tag which would be the name and the value.
+ *
+ * Since: 3.0.0
+ */
+
+G_BEGIN_DECLS
+
+/**
+ * purple_tags_new:
+ *
+ * Creates a new tags object.
+ *
+ * Returns: (transfer full): The new tags object.
+ *
+ * Since: 3.0.0
+ */
+PurpleTags *purple_tags_new(void);
+
+/**
+ * purple_tags_lookup:
+ * @tags: The instance.
+ * @name: The name of the tag to check if it exists.
+ * @found: (out) (nullable): A return address for a boolean on whether the tag
+ * was found or not.
+ *
+ * Gets the value of @name if it exists in @tags.
+ *
+ * If @found is non %NULL, it will be set to %TRUE if @name was found.
+ *
+ * Returns: The value of the first tag matching @name. If there is no value,
+ * %NULL is returned.
+ *
+ * Since: 3.0.0
+ */
+const gchar *purple_tags_lookup(PurpleTags *tags, const gchar *name, gboolean *found);
+
+/**
+ * purple_tags_get:
+ * @tags: The instance.
+ * @name: The name of the tag to get.
+ *
+ * Gets the first tag that matches @name.
+ *
+ * Returns: The value of the first tag matching @name. If there is no value,
+ * %NULL is returned.
+ *
+ * Since: 3.0.0
+ */
+const gchar *purple_tags_get(PurpleTags *tags, const gchar *name);
+
+/**
+ * purple_tags_add:
+ * @tags: The instance.
+ * @tag: The tag data.
+ *
+ * Adds @tag to @tags.
+ *
+ * Since: 3.0.0
+ */
+void purple_tags_add(PurpleTags *tags, const gchar *tag);
+
+/**
+ * purple_tags_remove:
+ * @tags: The instance.
+ * @tag: The tag data.
+ *
+ * Removes the first occurrence of @tag from @tags. Note that this is the tag
+ * name and value not just the name.
+ *
+ * Returns: %TRUE if @tag was found and removed, otherwise %FALSE.
+ *
+ * Since: 3.0.0
+ */
+gboolean purple_tags_remove(PurpleTags *tags, const gchar *tag);
+
+/**
+ * purple_tags_get_count:
+ * @tags: The instance.
+ *
+ * Gets the number of tags in @tags.
+ *
+ * Returns: The number of tags that @tags is keeping track of.
+ *
+ * Since: 3.0.0
+ */
+guint purple_tags_get_count(PurpleTags *tags);
+
+/**
+ * purple_tags_get_all:
+ * @tags: The instance.
+ *
+ * Gets a list of all the tags.
+ *
+ * Returns: (transfer none) (element-type utf8): The list of all the tags.
+ *
+ * Since: 3.0.0
+ */
+GList *purple_tags_get_all(PurpleTags *tags);
+
+/**
+ * purple_tags_to_string:
+ * @tags: The instance.
+ * @separator: (nullable): A string to separate the items.
+ *
+ * Creates a @separator delimited string containing each item in @tags.
+ *
+ * Returns: (transfer full): The string representation.
+ *
+ * Since: 3.0.0
+ */
+gchar *purple_tags_to_string(PurpleTags *tags, const gchar *separator);
+
+G_END_DECLS
+
+#endif /* PURPLE_TAGS_H */
--- a/libpurple/tests/meson.build Thu Sep 22 23:26:05 2022 -0500
+++ b/libpurple/tests/meson.build Fri Sep 23 00:13:38 2022 -0500
@@ -16,6 +16,7 @@
'protocol_xfer',
'purplepath',
'queued_output_stream',
+ 'tags',
'util',
'whiteboard_manager',
'xmlnode',
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/tests/test_tags.c Fri Sep 23 00:13:38 2022 -0500
@@ -0,0 +1,285 @@
+/*
+ * 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 program 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 program 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 program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <glib.h>
+
+#include <purple.h>
+
+/******************************************************************************
+ * Tests
+ *****************************************************************************/
+static void
+test_purple_tags_lookup_exists(void) {
+ PurpleTags *tags = purple_tags_new();
+ gboolean found = FALSE;
+ const gchar *value = NULL;
+
+ purple_tags_add(tags, "foo");
+ value = purple_tags_lookup(tags, "foo", &found);
+ g_assert_null(value);
+ g_assert_true(found);
+
+ purple_tags_add(tags, "bar:baz");
+ value = purple_tags_lookup(tags, "bar", &found);
+ g_assert_cmpstr(value, ==, "baz");
+ g_assert_true(found);
+
+ /* make sure that a name of pur doesn't match a tag of purple */
+ purple_tags_add(tags, "purple");
+ value = purple_tags_lookup(tags, "pur", &found);
+ g_assert_null(value);
+ g_assert_false(found);
+
+ g_clear_object(&tags);
+}
+
+static void
+test_purple_tags_lookup_non_existent(void) {
+ PurpleTags *tags = purple_tags_new();
+ gboolean found = FALSE;
+ const gchar *value;
+
+ value = purple_tags_lookup(tags, "foo", &found);
+ g_assert_null(value);
+ g_assert_false(found);
+
+ g_clear_object(&tags);
+}
+
+static void
+test_purple_tags_add_remove_bare(void) {
+ PurpleTags *tags = purple_tags_new();
+
+ purple_tags_add(tags, "tag1");
+ g_assert_cmpuint(purple_tags_get_count(tags), ==, 1);
+
+ purple_tags_remove(tags, "tag1");
+ g_assert_cmpuint(purple_tags_get_count(tags), ==, 0);
+
+ g_clear_object(&tags);
+}
+
+static void
+test_purple_tags_add_duplicate_bare(void) {
+ PurpleTags *tags = purple_tags_new();
+
+ purple_tags_add(tags, "tag1");
+ g_assert_cmpuint(purple_tags_get_count(tags), ==, 1);
+
+ purple_tags_add(tags, "tag1");
+ g_assert_cmpuint(purple_tags_get_count(tags), ==, 2);
+
+ g_clear_object(&tags);
+}
+
+static void
+test_purple_tags_remove_non_existent_bare(void) {
+ PurpleTags *tags = purple_tags_new();
+
+ purple_tags_remove(tags, "tag1");
+ g_assert_cmpuint(purple_tags_get_count(tags), ==, 0);
+
+ g_clear_object(&tags);
+}
+
+static void
+test_purple_tags_add_remove_with_value(void) {
+ PurpleTags *tags = purple_tags_new();
+
+ purple_tags_add(tags, "tag1:purple");
+ g_assert_cmpuint(purple_tags_get_count(tags), ==, 1);
+
+ purple_tags_remove(tags, "tag1:purple");
+ g_assert_cmpuint(purple_tags_get_count(tags), ==, 0);
+
+ g_clear_object(&tags);
+}
+
+static void
+test_purple_tags_add_duplicate_with_value(void) {
+ PurpleTags *tags = purple_tags_new();
+
+ purple_tags_add(tags, "tag1:purple");
+ g_assert_cmpuint(purple_tags_get_count(tags), ==, 1);
+
+ purple_tags_add(tags, "tag1:purple");
+ g_assert_cmpuint(purple_tags_get_count(tags), ==, 2);
+
+ g_clear_object(&tags);
+}
+
+static void
+test_purple_tags_remove_non_existent_with_value(void) {
+ PurpleTags *tags = purple_tags_new();
+
+ purple_tags_remove(tags, "tag1:purple");
+ g_assert_cmpuint(purple_tags_get_count(tags), ==, 0);
+
+ g_clear_object(&tags);
+}
+
+static void
+test_purple_tags_get_single(void) {
+ PurpleTags *tags = purple_tags_new();
+ const gchar *value = NULL;
+
+ purple_tags_add(tags, "tag1:purple");
+ value = purple_tags_get(tags, "tag1");
+
+ g_assert_cmpstr(value, ==, "purple");
+
+ g_clear_object(&tags);
+}
+
+static void
+test_purple_tags_get_multiple(void) {
+ PurpleTags *tags = purple_tags_new();
+ const gchar *value = NULL;
+
+ purple_tags_add(tags, "tag1:purple");
+ purple_tags_add(tags, "tag1:pink");
+
+ value = purple_tags_get(tags, "tag1");
+
+ g_assert_cmpstr(value, ==, "purple");
+
+ g_clear_object(&tags);
+}
+
+static void
+test_purple_tags_get_all(void) {
+ PurpleTags *tags = purple_tags_new();
+ GList *all_tags = NULL;
+ const gchar *values[] = {"foo", "bar", "baz", "qux", "quux", NULL};
+ gint i = 0;
+ gint len = 0;
+
+ for(i = 0; values[i] != NULL; i++) {
+ purple_tags_add(tags, values[i]);
+ len++;
+ }
+
+ all_tags = purple_tags_get_all(tags);
+ i = 0;
+
+ for(GList *l = all_tags; l != NULL; l = l->next) {
+ const gchar *value = l->data;
+
+ g_assert_cmpint(i, <, len);
+ g_assert_cmpstr(value, ==, values[i]);
+
+ i++;
+ }
+
+ g_assert_cmpint(i, ==, len);
+
+ g_clear_object(&tags);
+}
+
+static void
+test_purple_tags_to_string_single(void) {
+ PurpleTags *tags = purple_tags_new();
+ gchar *value = NULL;
+
+ purple_tags_add(tags, "foo");
+ value = purple_tags_to_string(tags, NULL);
+
+ g_assert_cmpstr(value, ==, "foo");
+
+ g_free(value);
+
+ g_clear_object(&tags);
+}
+
+static void
+test_purple_tags_to_string_multiple_with_separator(void) {
+ PurpleTags *tags = purple_tags_new();
+ gchar *value = NULL;
+
+ purple_tags_add(tags, "foo");
+ purple_tags_add(tags, "bar");
+ purple_tags_add(tags, "baz");
+ value = purple_tags_to_string(tags, ", ");
+
+ g_assert_cmpstr(value, ==, "foo, bar, baz");
+
+ g_free(value);
+
+ g_clear_object(&tags);
+}
+
+static void
+test_purple_tags_to_string_multiple_with_null_separator(void) {
+ PurpleTags *tags = purple_tags_new();
+ gchar *value = NULL;
+
+ purple_tags_add(tags, "foo");
+ purple_tags_add(tags, "bar");
+ purple_tags_add(tags, "baz");
+ value = purple_tags_to_string(tags, NULL);
+
+ g_assert_cmpstr(value, ==, "foobarbaz");
+
+ g_free(value);
+
+ g_clear_object(&tags);
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+gint
+main(gint argc, gchar **argv) {
+ g_test_init(&argc, &argv, NULL);
+
+ g_test_add_func("/tags/lookup-exists", test_purple_tags_lookup_exists);
+ g_test_add_func("/tags/lookup-non-existent",
+ test_purple_tags_lookup_non_existent);
+
+ g_test_add_func("/tags/add-remove-bare",
+ test_purple_tags_add_remove_bare);
+ g_test_add_func("/tags/add-duplicate-bare",
+ test_purple_tags_add_duplicate_bare);
+ g_test_add_func("/tags/remove-non-existent-bare",
+ test_purple_tags_remove_non_existent_bare);
+
+ g_test_add_func("/tags/add-remove-with-value",
+ test_purple_tags_add_remove_with_value);
+ g_test_add_func("/tags/add-duplicate-with-value",
+ test_purple_tags_add_duplicate_with_value);
+ g_test_add_func("/tags/remove-non-existent-with-value",
+ test_purple_tags_remove_non_existent_with_value);
+
+ g_test_add_func("/tags/get-single", test_purple_tags_get_single);
+ g_test_add_func("/tags/get-multiple", test_purple_tags_get_multiple);
+
+ g_test_add_func("/tags/get-all", test_purple_tags_get_all);
+
+ g_test_add_func("/tags/to-string-single",
+ test_purple_tags_to_string_single);
+ g_test_add_func("/tags/to-string-multiple-with-separator",
+ test_purple_tags_to_string_multiple_with_separator);
+ g_test_add_func("/tags/to-string-multiple-with-null-separator",
+ test_purple_tags_to_string_multiple_with_null_separator);
+
+ return g_test_run();
+}