diff src/log.c @ 11025:8d2007d738d5

[gaim-migrate @ 12899] sf patch #1180490, from Richard Laager (who else?) A pretty peach of a patch that allows you to auto-complete screen names based on log file names. committer: Tailor Script <tailor@pidgin.im>
author Mark Doliner <mark@kingant.net>
date Thu, 23 Jun 2005 03:04:52 +0000
parents 5e41c817dfa2
children 50224ac8184d
line wrap: on
line diff
--- a/src/log.c	Thu Jun 23 02:24:22 2005 +0000
+++ b/src/log.c	Thu Jun 23 03:04:52 2005 +0000
@@ -43,6 +43,7 @@
 };
 static GHashTable *logsize_users = NULL;
 
+static GList *log_get_log_sets_common();
 
 /**************************************************************************
  * PUBLIC LOGGING FUNCTIONS ***********************************************
@@ -238,15 +239,23 @@
 				void(*finalize)(GaimLog *),
 				GList*(*list)(GaimLogType type, const char*, GaimAccount*),
 				char*(*read)(GaimLog*, GaimLogReadFlags*),
-				int(*size)(GaimLog*))
+				int(*size)(GaimLog*),
+				int(*total_size)(GaimLogType type, const char *name, GaimAccount *account),
+				GList*(*list_syslog)(GaimAccount *account),
+				GList*(*get_log_sets)(void))
 {
 	GaimLogLogger *logger = g_new0(GaimLogLogger, 1);
+
 	logger->create = create;
 	logger->write = write;
 	logger->finalize = finalize;
 	logger->list = list;
 	logger->read = read;
 	logger->size = size;
+	logger->total_size = total_size;
+	logger->list_syslog = list_syslog;
+	logger->get_log_sets = get_log_sets;
+
 	return logger;
 }
 
@@ -319,6 +328,103 @@
 	return g_list_sort(logs, gaim_log_compare);
 }
 
+gint gaim_log_set_compare(gconstpointer y, gconstpointer z)
+{
+	const GaimLogSet *a = y;
+	const GaimLogSet *b = z;
+	gint ret = 0;
+	char *tmp;
+
+	/* This logic seems weird at first...
+	 * If either account is NULL, we pretend the accounts are
+	 * equal. This allows us to detect duplicates that will
+	 * exist if one logger knows the account and another
+	 * doesn't. */
+	if (a->account != NULL && b->account != NULL) {
+		ret = gaim_utf8_strcasecmp(gaim_account_get_username(a->account), gaim_account_get_username(b->account));
+		if (ret != 0)
+			return ret;
+	}
+
+	tmp = g_strdup(gaim_normalize(a->account, a->name));
+	ret = gaim_utf8_strcasecmp(tmp, gaim_normalize(b->account, b->name));
+	g_free(tmp);
+	if (ret != 0)
+		return ret;
+
+	return (gint)b->type - (gint)a->type;
+}
+
+guint log_set_hash(gconstpointer key)
+{
+	const GaimLogSet *set = key;
+
+	/* The account isn't hashed because we need GaimLogSets with NULL accounts
+	 * to be found when we search by a GaimLogSet that has a non-NULL account
+	 * but the same type and name. */
+	return g_int_hash((gint *)&set->type) + g_str_hash(set->name);
+}
+
+gboolean log_set_equal(gconstpointer a, gconstpointer b)
+{
+	/* I realize that the choices made for GList and GHashTable
+	 * make sense for those data types, but I wish the comparison
+	 * functions were compatible. */
+	return !gaim_log_set_compare(a, b);
+}
+
+void log_set_build_list(gpointer key, gpointer value, gpointer user_data)
+{
+	*((GList **)user_data) = g_list_append(*((GList **)user_data), key);
+}
+
+GList *gaim_log_get_log_sets()
+{
+	GSList *n;
+	GList *sets = NULL;
+	GList *set;
+	GHashTable *sets_ht = g_hash_table_new(log_set_hash, log_set_equal);
+
+	/* Get the log sets from all the loggers. */
+	for (n = loggers; n; n = n->next) {
+		GaimLogLogger *logger = n->data;
+
+		if (!logger->get_log_sets)
+			continue;
+
+		sets = g_list_concat(sets, logger->get_log_sets());
+	}
+
+	/* Get the log sets for loggers that use the common logger functions. */
+	sets = g_list_concat(sets, log_get_log_sets_common());
+
+	for (set = sets; set != NULL ; set = set->next) {
+		GaimLogSet *existing_set = g_hash_table_lookup(sets_ht, set->data);
+
+		if (existing_set == NULL) {
+			g_hash_table_insert(sets_ht, set->data, set->data);
+		} else if (existing_set->account == NULL && ((GaimLogSet *)set->data)->account != NULL) {
+			/* The existing entry in the hash table has no account.
+			 * This one does. We'll delete the old one and keep this one. */
+			g_hash_table_replace(sets_ht, set->data, set->data);
+			g_free(existing_set->name);
+			g_free(existing_set);
+		} else {
+			g_free(((GaimLogSet *)set->data)->name);
+			g_free(set->data);
+		}
+	}
+	g_list_free(sets);
+
+	/* At this point, we've built a GHashTable of unique GaimLogSets.
+	 * So, we build a list of those keys and destroy the GHashTable. */
+	sets = NULL;
+	g_hash_table_foreach(sets_ht, log_set_build_list, &sets);
+	g_hash_table_destroy(sets_ht);
+
+	return g_list_sort(sets, gaim_log_set_compare);
+}
+
 GList *gaim_log_get_system_logs(GaimAccount *account)
 {
 	GList *logs = NULL;
@@ -446,6 +552,124 @@
 	return st.st_size;
 }
 
+/* This will build log sets for all loggers that use the common logger
+ * functions because they use the same directory structure. */
+static GList *log_get_log_sets_common()
+{
+	gchar *log_path = g_build_filename(gaim_user_dir(), "logs", NULL);
+	GDir *log_dir = g_dir_open(log_path, 0, NULL);
+	const gchar *protocol;
+	GList *sets = NULL;
+
+	if (log_dir == NULL) {
+		g_free(log_path);
+		return NULL;
+	}
+
+	while ((protocol = g_dir_read_name(log_dir)) != NULL) {
+		gchar *protocol_path = g_build_filename(log_path, protocol, NULL);
+		GDir *protocol_dir;
+		const gchar *username;
+		gchar *protocol_unescaped;
+		GList *account_iter;
+		GList *accounts = NULL;
+
+		if ((protocol_dir = g_dir_open(protocol_path, 0, NULL)) == NULL) {
+			g_free(protocol_path);
+			continue;
+		}
+
+		/* Using g_strdup() to cover the one-in-a-million chance that a
+		 * prpl's list_icon function uses gaim_unescape_filename(). */
+		protocol_unescaped = g_strdup(gaim_unescape_filename(protocol));
+
+		/* Find all the accounts for protocol. */
+		for (account_iter = gaim_accounts_get_all() ; account_iter != NULL ; account_iter = account_iter->next) {
+			GaimPlugin *prpl;
+			GaimPluginProtocolInfo *prpl_info;
+		
+			prpl = gaim_find_prpl(gaim_account_get_protocol_id((GaimAccount *)account_iter->data));
+			if (!prpl)
+				continue;
+			prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl);
+
+			if (!strcmp(protocol_unescaped, prpl_info->list_icon((GaimAccount *)account_iter->data, NULL)))
+				accounts = g_list_append(accounts, account_iter->data);
+		}
+		g_free(protocol_unescaped);
+
+		while ((username = g_dir_read_name(protocol_dir)) != NULL) {
+			gchar *username_path = g_build_filename(protocol_path, username, NULL);
+			GDir *username_dir;
+			const gchar *username_unescaped;
+			GaimAccount *account = NULL;
+			gchar *name;
+
+			if ((username_dir = g_dir_open(username_path, 0, NULL)) == NULL) {
+				g_free(username_path);
+				continue;
+			}
+
+			/* Find the account for username in the list of accounts for protocol. */
+			username_unescaped = gaim_unescape_filename(username);
+			for (account_iter = g_list_first(accounts) ; account_iter != NULL ; account_iter = account_iter->next) {
+				if (!strcmp(((GaimAccount *)account_iter->data)->username, username_unescaped)) {
+					account = account_iter->data;
+					break;
+				}
+			}
+
+			/* Don't worry about the cast, name will point to dynamically allocated memory shortly. */
+			while ((name = (gchar *)g_dir_read_name(username_dir)) != NULL) {
+				size_t len;
+				GaimLogSet *set = g_new0(GaimLogSet, 1);
+
+				/* Unescape the filename. */
+				name = g_strdup(gaim_unescape_filename(name));
+
+				/* Get the (possibly new) length of name. */
+				len = strlen(name);
+
+				set->account = account;
+				set->name = name;
+
+				/* Chat for .chat or .system at the end of the name to determine the type. */
+				set->type = GAIM_LOG_IM;
+				if (len > 7) {
+					gchar *tmp = &name[len - 7];
+					if (!strcmp(tmp, ".system")) {
+						set->type = GAIM_LOG_SYSTEM;
+						*tmp = '\0';
+					}
+				}
+				if (len > 5) {
+					gchar *tmp = &name[len - 5];
+					if (!strcmp(tmp, ".chat")) {
+						set->type = GAIM_LOG_CHAT;
+						*tmp = '\0';
+					}
+				}
+
+				/* Determine if this (account, name) combination exists as a buddy. */
+				if (gaim_find_buddy(account, name) != NULL)
+					set->buddy = TRUE;
+				else
+					set->buddy = FALSE;
+
+				sets = g_list_append(sets, set);
+			}
+			g_free(username_path);
+			g_dir_close(username_dir);
+		}
+		g_free(protocol_path);
+		g_dir_close(protocol_dir);
+	}
+	g_free(log_path);
+	g_dir_close(log_dir);
+
+	return sets;
+}
+
 #if 0 /* Maybe some other time. */
 /****************
  ** XML LOGGER **
@@ -541,6 +765,7 @@
 	xml_logger_finalize,
 	xml_logger_list,
 	NULL,
+	NULL,
 	NULL
 };
 #endif
@@ -672,7 +897,8 @@
 	html_logger_read,
 	gaim_log_common_sizer,
 	NULL,
-	html_logger_list_syslog
+	html_logger_list_syslog,
+	NULL
 };
 
 
@@ -804,7 +1030,8 @@
 	txt_logger_read,
 	gaim_log_common_sizer,
 	NULL,
-	txt_logger_list_syslog
+	txt_logger_list_syslog,
+	NULL
 };
 
 /****************
@@ -991,6 +1218,89 @@
 	return data ? data->length : 0;
 }
 
+static GList *old_logger_get_log_sets()
+{
+	char *log_path = g_build_filename(gaim_user_dir(), "logs", NULL);
+	GDir *log_dir = g_dir_open(log_path, 0, NULL);
+	gchar *name;
+	GList *sets = NULL;
+	GaimBlistNode *gnode, *cnode, *bnode;
+
+	g_free(log_path);
+	if (log_dir == NULL)
+		return NULL;
+
+	/* Don't worry about the cast, name will be filled with a dynamically allocated data shortly. */
+	while ((name = (gchar *)g_dir_read_name(log_dir)) != NULL) {
+		size_t len;
+		gchar *ext;
+		GaimLogSet *set;
+		gboolean found = FALSE;
+
+		/* Unescape the filename. */
+		name = g_strdup(gaim_unescape_filename(name));
+
+		/* Get the (possibly new) length of name. */
+		len = strlen(name);
+
+		if (len < 5) {
+			g_free(name);
+			continue;
+		}
+
+		/* Make sure we're dealing with a log file. */
+		ext = &name[len - 4];
+		if (strcmp(ext, ".log")) {
+			g_free(name);
+			continue;
+		}
+
+		set = g_new0(GaimLogSet, 1);
+
+		/* Chat for .chat at the end of the name to determine the type. */
+		*ext = '\0';
+		set->type = GAIM_LOG_IM;
+		if (len > 9) {
+			char *tmp = &name[len - 9];
+			if (!strcmp(tmp, ".chat")) {
+				set->type = GAIM_LOG_CHAT;
+				*tmp = '\0';
+			}
+		}
+
+		set->name = name;
+
+		/* Search the buddy list to find the account and to determine if this is a buddy. */
+		for (gnode = gaim_get_blist()->root; !found && gnode != NULL; gnode = gnode->next)
+		{
+			if (!GAIM_BLIST_NODE_IS_GROUP(gnode))
+				continue;
+
+			for (cnode = gnode->child; !found && cnode != NULL; cnode = cnode->next)
+			{
+				if (!GAIM_BLIST_NODE_IS_CONTACT(cnode))
+					continue;
+
+				for (bnode = cnode->child; !found && bnode != NULL; bnode = bnode->next)
+				{
+					GaimBuddy *buddy = (GaimBuddy *)bnode;
+
+					if (!strcmp(buddy->name, name)) {
+						set->account = buddy->account;
+						set->buddy = TRUE;
+						found = TRUE;
+					}
+				}
+			}
+		}
+
+		sets = g_list_append(sets, set);
+	}
+	g_dir_close(log_dir);
+
+	return sets;
+}
+
 static void old_logger_finalize(GaimLog *log)
 {
 	struct old_logger_data *data = log->logger_data;
@@ -1006,5 +1316,6 @@
 	old_logger_read,
 	old_logger_size,
 	old_logger_total_size,
-	NULL
+	NULL,
+	old_logger_get_log_sets
 };