Fri, 23 Sep 2022 00:13:38 -0500
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(); +}