--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/purplemessage.c Mon Jan 11 01:08:47 2021 -0600 @@ -0,0 +1,573 @@ +/* + * 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/gi18n-lib.h> + +#include "internal.h" + +#include "debug.h" +#include "purplemessage.h" +#include "enums.h" +#include "purpleprivate.h" + +/** + * PurpleMessage: + * + * A message data container. + */ +struct _PurpleMessage { + GObject parent; + + guint id; + gchar *author; + gchar *author_alias; + gchar *recipient; + + gchar *contents; + PurpleMessageContentType content_type; + + GDateTime *timestamp; + PurpleMessageFlags flags; + + GHashTable *attachments; +}; + +enum { + PROP_0, + PROP_ID, + PROP_AUTHOR, + PROP_AUTHOR_ALIAS, + PROP_RECIPIENT, + PROP_CONTENTS, + PROP_CONTENT_TYPE, + PROP_TIMESTAMP, + PROP_FLAGS, + N_PROPERTIES +}; +static GParamSpec *properties[N_PROPERTIES]; + +G_DEFINE_TYPE(PurpleMessage, purple_message, G_TYPE_OBJECT) + +/****************************************************************************** + * Helpers + *****************************************************************************/ +static void +purple_message_set_id(PurpleMessage *message, guint id) { + message->id = id; + + g_object_notify_by_pspec(G_OBJECT(message), properties[PROP_ID]); +} + +static void +purple_message_set_author(PurpleMessage *message, const gchar *author) { + g_free(message->author); + message->author = g_strdup(author); + + g_object_notify_by_pspec(G_OBJECT(message), properties[PROP_AUTHOR]); +} + +/****************************************************************************** + * GObject Implementation + *****************************************************************************/ +static void +purple_message_get_property(GObject *object, guint param_id, GValue *value, + GParamSpec *pspec) +{ + PurpleMessage *message = PURPLE_MESSAGE(object); + + switch(param_id) { + case PROP_ID: + g_value_set_uint(value, purple_message_get_id(message)); + break; + case PROP_AUTHOR: + g_value_set_string(value, purple_message_get_author(message)); + break; + case PROP_AUTHOR_ALIAS: + g_value_set_string(value, purple_message_get_author_alias(message)); + break; + case PROP_RECIPIENT: + g_value_set_string(value, purple_message_get_recipient(message)); + break; + case PROP_CONTENTS: + g_value_set_string(value, purple_message_get_contents(message)); + break; + case PROP_CONTENT_TYPE: + g_value_set_enum(value, purple_message_get_content_type(message)); + break; + case PROP_TIMESTAMP: + g_value_set_boxed(value, purple_message_get_timestamp(message)); + break; + case PROP_FLAGS: + g_value_set_flags(value, purple_message_get_flags(message)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec); + break; + } +} + +static void +purple_message_set_property(GObject *object, guint param_id, + const GValue *value, GParamSpec *pspec) +{ + PurpleMessage *message = PURPLE_MESSAGE(object); + + switch(param_id) { + case PROP_ID: + purple_message_set_id(message, g_value_get_uint(value)); + break; + case PROP_AUTHOR: + purple_message_set_author(message, g_value_get_string(value)); + break; + case PROP_AUTHOR_ALIAS: + purple_message_set_author_alias(message, g_value_get_string(value)); + break; + case PROP_RECIPIENT: + purple_message_set_recipient(message, g_value_get_string(value)); + break; + case PROP_CONTENTS: + purple_message_set_contents(message, g_value_get_string(value)); + break; + case PROP_CONTENT_TYPE: + purple_message_set_content_type(message, g_value_get_enum(value)); + break; + case PROP_TIMESTAMP: + purple_message_set_timestamp(message, g_value_get_boxed(value)); + break; + case PROP_FLAGS: + purple_message_set_flags(message, g_value_get_flags(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec); + break; + } +} + +static void +purple_message_finalize(GObject *obj) { + PurpleMessage *message = PURPLE_MESSAGE(obj); + + g_free(message->author); + g_free(message->author_alias); + g_free(message->recipient); + g_free(message->contents); + + if(message->timestamp != NULL) { + g_date_time_unref(message->timestamp); + } + + g_hash_table_destroy(message->attachments); + + G_OBJECT_CLASS(purple_message_parent_class)->finalize(obj); +} + +static void +purple_message_init(PurpleMessage *message) { + message->attachments = g_hash_table_new_full(g_int64_hash, g_int64_equal, + NULL, g_object_unref); +} + +static void +purple_message_class_init(PurpleMessageClass *klass) { + GObjectClass *obj_class = G_OBJECT_CLASS(klass); + + obj_class->get_property = purple_message_get_property; + obj_class->set_property = purple_message_set_property; + obj_class->finalize = purple_message_finalize; + + /** + * PurpleMessage::id: + * + * The account specific identifier of the message. + */ + properties[PROP_ID] = g_param_spec_uint( + "id", "ID", + "The session-unique message id", + 0, G_MAXUINT, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /** + * PurpleMessage::author: + * + * The author of the message. + */ + properties[PROP_AUTHOR] = g_param_spec_string( + "author", "Author", + "The username of the person, who sent the message.", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * PurpleMessage::author-alias: + * + * The alias of the author. + */ + properties[PROP_AUTHOR_ALIAS] = g_param_spec_string( + "author-alias", "Author's alias", + "The alias of the sender", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * PurpleMessage::recipient: + * + * The recipient of the message. + */ + properties[PROP_RECIPIENT] = g_param_spec_string( + "recipient", "Recipient", + "The username of the recipient.", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * PurpleMessage::content: + * + * The contents of the message. + */ + properties[PROP_CONTENTS] = g_param_spec_string( + "contents", "Contents", + "The message text", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * PurpleMessage::content-type: + * + * The content-type of the message. + */ + properties[PROP_CONTENT_TYPE] = g_param_spec_enum( + "content-type", "content-type", + "The content-type of the message.", + PURPLE_TYPE_MESSAGE_CONTENT_TYPE, PURPLE_MESSAGE_CONTENT_TYPE_PLAIN, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * PurpleMessage::timestamp: + * + * The timestamp of the message. + */ + properties[PROP_TIMESTAMP] = g_param_spec_boxed( + "timestamp", "timestamp", + "The timestamp of the message", + G_TYPE_DATE_TIME, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * PurpleMessage::flags: + * + * The #PurpleMessageFlags for the message. + */ + properties[PROP_FLAGS] = g_param_spec_flags( + "flags", "Flags", + "Bitwise set of #PurpleMessageFlags flags", + PURPLE_TYPE_MESSAGE_FLAGS, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(obj_class, N_PROPERTIES, properties); +} + +/****************************************************************************** + * Public API + *****************************************************************************/ +PurpleMessage * +purple_message_new_outgoing(const gchar *author, const gchar *recipient, + const gchar *contents, PurpleMessageFlags flags) +{ + PurpleMessage *message = NULL; + GDateTime *dt = NULL; + + g_warn_if_fail(!(flags & PURPLE_MESSAGE_RECV)); + g_warn_if_fail(!(flags & PURPLE_MESSAGE_SYSTEM)); + + flags |= PURPLE_MESSAGE_SEND; + dt = g_date_time_new_now_local(); + + /* who may be NULL for outgoing MUC messages */ + message = PURPLE_MESSAGE(g_object_new(PURPLE_TYPE_MESSAGE, + "author", author, + "recipient", recipient, + "contents", contents, + "timestamp", dt, + "flags", flags, + NULL)); + + g_date_time_unref(dt); + + return message; +} + +PurpleMessage * +purple_message_new_incoming(const gchar *who, const gchar *contents, + PurpleMessageFlags flags, guint64 timestamp) +{ + PurpleMessage *message = NULL; + GDateTime *dt = NULL; + + g_warn_if_fail(!(flags & PURPLE_MESSAGE_SEND)); + g_warn_if_fail(!(flags & PURPLE_MESSAGE_SYSTEM)); + + flags |= PURPLE_MESSAGE_RECV; + + if(timestamp == 0) { + dt = g_date_time_new_now_local(); + } else { + dt = g_date_time_new_from_unix_local((gint64)timestamp); + } + + message = PURPLE_MESSAGE(g_object_new(PURPLE_TYPE_MESSAGE, + "author", who, + "author-alias", who, + "contents", contents, + "timestamp", dt, + "flags", flags, + NULL)); + + g_date_time_unref(dt); + + return message; +} + +PurpleMessage * +purple_message_new_system(const gchar *contents, PurpleMessageFlags flags) { + PurpleMessage *message = NULL; + GDateTime *dt = NULL; + + g_warn_if_fail(!(flags & PURPLE_MESSAGE_SEND)); + g_warn_if_fail(!(flags & PURPLE_MESSAGE_RECV)); + + flags |= PURPLE_MESSAGE_SYSTEM; + dt = g_date_time_new_now_local(); + + message = PURPLE_MESSAGE(g_object_new(PURPLE_TYPE_MESSAGE, + "contents", contents, + "timestamp", dt, + "flags", flags, + NULL)); + + g_date_time_unref(dt); + + return message; +} + +guint +purple_message_get_id(PurpleMessage *message) { + g_return_val_if_fail(PURPLE_IS_MESSAGE(message), 0); + + return message->id; +} + +const gchar * +purple_message_get_author(PurpleMessage *message) { + g_return_val_if_fail(PURPLE_IS_MESSAGE(message), NULL); + + return message->author; +} + +void +purple_message_set_recipient(PurpleMessage *message, const gchar *recipient) { + g_return_if_fail(PURPLE_IS_MESSAGE(message)); + + g_free(message->recipient); + message->recipient = g_strdup(recipient); + + g_object_notify_by_pspec(G_OBJECT(message), properties[PROP_RECIPIENT]); +} + +const gchar * +purple_message_get_recipient(PurpleMessage *message) { + g_return_val_if_fail(PURPLE_IS_MESSAGE(message), NULL); + + return message->recipient; +} + +void +purple_message_set_author_alias(PurpleMessage *message, + const gchar *author_alias) +{ + g_return_if_fail(PURPLE_IS_MESSAGE(message)); + + g_free(message->author_alias); + message->author_alias = g_strdup(author_alias); + + g_object_notify_by_pspec(G_OBJECT(message), properties[PROP_AUTHOR_ALIAS]); +} + +const gchar * +purple_message_get_author_alias(PurpleMessage *message) { + g_return_val_if_fail(PURPLE_IS_MESSAGE(message), NULL); + + if (message->author_alias == NULL) + return purple_message_get_author(message); + + return message->author_alias; +} + +void +purple_message_set_contents(PurpleMessage *message, const gchar *contents) { + g_return_if_fail(PURPLE_IS_MESSAGE(message)); + + g_free(message->contents); + message->contents = g_strdup(contents); + + g_object_notify_by_pspec(G_OBJECT(message), properties[PROP_CONTENTS]); +} + +const gchar * +purple_message_get_contents(PurpleMessage *message) { + g_return_val_if_fail(PURPLE_IS_MESSAGE(message), NULL); + + return message->contents; +} + +void +purple_message_set_content_type(PurpleMessage *message, + PurpleMessageContentType content_type) +{ + g_return_if_fail(PURPLE_IS_MESSAGE(message)); + + message->content_type = content_type; + + g_object_notify_by_pspec(G_OBJECT(message), properties[PROP_CONTENT_TYPE]); +} + +PurpleMessageContentType +purple_message_get_content_type(PurpleMessage *message) { + g_return_val_if_fail(PURPLE_IS_MESSAGE(message), + PURPLE_MESSAGE_CONTENT_TYPE_PLAIN); + + return message->content_type; +} + +gboolean +purple_message_is_empty(PurpleMessage *message) { + return (message->contents == NULL || message->contents[0] == '\0'); +} + +void +purple_message_set_timestamp(PurpleMessage *message, GDateTime *timestamp) { + g_return_if_fail(PURPLE_IS_MESSAGE(message)); + + g_clear_pointer(&message->timestamp, g_date_time_unref); + if(timestamp != NULL) { + message->timestamp = g_date_time_ref(timestamp); + } + + g_object_notify_by_pspec(G_OBJECT(message), properties[PROP_TIMESTAMP]); +} + +GDateTime * +purple_message_get_timestamp(PurpleMessage *message) { + g_return_val_if_fail(PURPLE_IS_MESSAGE(message), 0); + + if(message->timestamp == NULL) { + GDateTime *dt = g_date_time_new_now_local(); + purple_message_set_timestamp(message, dt); + g_date_time_unref(dt); + } + + return message->timestamp; +} + +gchar * +purple_message_format_timestamp(PurpleMessage *message, const gchar *format) { + GDateTime *dt = NULL; + + g_return_val_if_fail(PURPLE_IS_MESSAGE(message), NULL); + g_return_val_if_fail(format != NULL, NULL); + + dt = purple_message_get_timestamp(message); + + return g_date_time_format(dt, format); +} + +void +purple_message_set_flags(PurpleMessage *message, PurpleMessageFlags flags) { + g_return_if_fail(PURPLE_IS_MESSAGE(message)); + + message->flags = flags; + + g_object_notify_by_pspec(G_OBJECT(message), properties[PROP_FLAGS]); +} + +PurpleMessageFlags +purple_message_get_flags(PurpleMessage *message) { + g_return_val_if_fail(PURPLE_IS_MESSAGE(message), 0); + + return message->flags; +} + +gboolean +purple_message_add_attachment(PurpleMessage *message, + PurpleAttachment *attachment) +{ + g_return_val_if_fail(PURPLE_IS_MESSAGE(message), FALSE); + g_return_val_if_fail(PURPLE_IS_ATTACHMENT(attachment), FALSE); + + return g_hash_table_insert(message->attachments, + purple_attachment_get_hash_key(attachment), + g_object_ref(G_OBJECT(attachment))); +} + +gboolean +purple_message_remove_attachment(PurpleMessage *message, guint64 id) { + g_return_val_if_fail(PURPLE_IS_MESSAGE(message), FALSE); + + return g_hash_table_remove(message->attachments, &id); +} + +PurpleAttachment * +purple_message_get_attachment(PurpleMessage *message, guint64 id) { + PurpleAttachment *attachment = NULL; + + g_return_val_if_fail(PURPLE_IS_MESSAGE(message), NULL); + + attachment = g_hash_table_lookup(message->attachments, &id); + if(PURPLE_IS_ATTACHMENT(attachment)) { + return PURPLE_ATTACHMENT(g_object_ref(G_OBJECT(attachment))); + } + + return NULL; +} + +void +purple_message_foreach_attachment(PurpleMessage *message, + PurpleAttachmentForeachFunc func, + gpointer data) +{ + GHashTableIter iter; + gpointer value; + + g_return_if_fail(PURPLE_IS_MESSAGE(message)); + g_return_if_fail(func != NULL); + + g_hash_table_iter_init(&iter, message->attachments); + while(g_hash_table_iter_next(&iter, NULL, &value)) { + func(PURPLE_ATTACHMENT(value), data); + } +} + +void +purple_message_clear_attachments(PurpleMessage *message) { + g_return_if_fail(PURPLE_IS_MESSAGE(message)); + + g_hash_table_remove_all(message->attachments); +}