From 6f4d1c3d77bf53fa39071036ea6c23f8186e2ce6 Mon Sep 17 00:00:00 2001 From: Alexey Golub Date: Thu, 7 Mar 2019 22:13:00 +0200 Subject: [PATCH] Refactor message grouping --- .../Internal/Extensions.cs | 31 +++++++++++++ .../Services/ExportService.TemplateModel.cs | 46 +++++-------------- .../Services/ExportService.cs | 36 +++------------ 3 files changed, 49 insertions(+), 64 deletions(-) diff --git a/DiscordChatExporter.Core/Internal/Extensions.cs b/DiscordChatExporter.Core/Internal/Extensions.cs index 84d96f46..ca3fb69d 100644 --- a/DiscordChatExporter.Core/Internal/Extensions.cs +++ b/DiscordChatExporter.Core/Internal/Extensions.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Drawing; +using System.Linq; using System.Net; namespace DiscordChatExporter.Core.Internal @@ -17,5 +19,34 @@ namespace DiscordChatExporter.Core.Internal public static Color ResetAlpha(this Color color) => Color.FromArgb(1, color); public static string HtmlEncode(this string value) => WebUtility.HtmlEncode(value); + + public static IEnumerable> GroupAdjacentWhile(this IEnumerable source, + Func, T, bool> groupPredicate) + { + // Create buffer + var buffer = new List(); + + // Enumerate source + foreach (var element in source) + { + // If buffer is not empty and group predicate failed - yield and flush buffer + if (buffer.Any() && !groupPredicate(buffer, element)) + { + yield return buffer; + buffer = new List(); // new instance to reset reference + } + + // Add element to buffer + buffer.Add(element); + } + + // If buffer still has something after the source has been enumerated - yield + if (buffer.Any()) + yield return buffer; + } + + public static IEnumerable> GroupAdjacentWhile(this IEnumerable source, + Func, bool> groupPredicate) + => source.GroupAdjacentWhile((buffer, _) => groupPredicate(buffer)); } } \ No newline at end of file diff --git a/DiscordChatExporter.Core/Services/ExportService.TemplateModel.cs b/DiscordChatExporter.Core/Services/ExportService.TemplateModel.cs index 2ee647da..1e2bfabc 100644 --- a/DiscordChatExporter.Core/Services/ExportService.TemplateModel.cs +++ b/DiscordChatExporter.Core/Services/ExportService.TemplateModel.cs @@ -27,45 +27,21 @@ namespace DiscordChatExporter.Core.Services } private IEnumerable GroupMessages(IEnumerable messages) - { - // Group adjacent messages by timestamp and author - var buffer = new List(); - foreach (var message in messages) + => messages.GroupAdjacentWhile((buffer, message) => { - // Group break condition - var breakCondition = - buffer.Any() && - ( - message.Author.Id != buffer.First().Author.Id || // when author changes - (message.Timestamp - buffer.Last().Timestamp).TotalMinutes > 7 // when more than 7 minutes passed since last message - ); + // Break group if the author changed + if (buffer.Last().Author.Id != message.Author.Id) + return false; - // If condition is true - flush buffer - if (breakCondition) - { - var group = new MessageGroup(buffer.First().Author, buffer.First().Timestamp, buffer); + // Break group if last message was more than 7 minutes ago + if ((message.Timestamp - buffer.Last().Timestamp).TotalMinutes > 7) + return false; - // Reset the buffer instead of clearing to avoid mutations on existing references - buffer = new List(); + return true; + }).Select(g => new MessageGroup(g.First().Author, g.First().Timestamp, g)); - yield return group; - } - - // Add message to buffer - buffer.Add(message); - } - - // Add what's remaining in buffer - if (buffer.Any()) - { - var group = new MessageGroup(buffer.First().Author, buffer.First().Timestamp, buffer); - - yield return group; - } - } - - private string Format(IFormattable obj, string format) => - obj.ToString(format, CultureInfo.InvariantCulture); + private string Format(IFormattable obj, string format) + => obj.ToString(format, CultureInfo.InvariantCulture); private string FormatDate(DateTime dateTime) => Format(dateTime, _dateFormat); diff --git a/DiscordChatExporter.Core/Services/ExportService.cs b/DiscordChatExporter.Core/Services/ExportService.cs index 3d1fc420..25b57e47 100644 --- a/DiscordChatExporter.Core/Services/ExportService.cs +++ b/DiscordChatExporter.Core/Services/ExportService.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; using System.IO; +using System.Linq; +using DiscordChatExporter.Core.Internal; using DiscordChatExporter.Core.Models; using Scriban; using Scriban.Runtime; @@ -82,34 +84,6 @@ namespace DiscordChatExporter.Core.Services } } - private IReadOnlyList SplitIntoPartitions(ChatLog chatLog, int partitionLimit) - { - var result = new List(); - - // Loop through all messages with an increment of partition limit - for (var i = 0; i < chatLog.Messages.Count; i += partitionLimit) - { - // Calculate how many messages left in total - var remainingMessageCount = chatLog.Messages.Count - i; - - // Decide how many messages are going into this partition - // Each partition will have the same number of messages except the last one that might have fewer (all remaining messages) - var partitionMessageCount = partitionLimit.ClampMax(remainingMessageCount); - - // Get messages that belong to this partition - var partitionMessages = new List(); - for (var j = i; j < i + partitionMessageCount; j++) - partitionMessages.Add(chatLog.Messages[j]); - - // Create a partition and add to list - var partition = new ChatLog(chatLog.Guild, chatLog.Channel, chatLog.From, chatLog.To, partitionMessages, - chatLog.Mentionables); - result.Add(partition); - } - - return result; - } - public void ExportChatLog(ChatLog chatLog, string filePath, ExportFormat format, int? partitionLimit = null) { @@ -121,7 +95,11 @@ namespace DiscordChatExporter.Core.Services // Otherwise split into partitions and export separately else { - var partitions = SplitIntoPartitions(chatLog, partitionLimit.Value); + // Create partitions by grouping up to X adjacent messages into separate chat logs + var partitions = chatLog.Messages.GroupAdjacentWhile(g => g.Count < partitionLimit.Value) + .Select(g => new ChatLog(chatLog.Guild, chatLog.Channel, chatLog.From, chatLog.To, g, chatLog.Mentionables)) + .ToArray(); + ExportChatLogPartitioned(partitions, filePath, format); } }