// // MessageSorter.cs // // Author: Jeffrey Stedfast // // Copyright (c) 2013-2020 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // using System; using System.Linq; using System.Collections.Generic; using MimeKit; using MailKit.Search; namespace MailKit { /// /// Routines for sorting messages. /// /// /// Routines for sorting messages. /// public static class MessageSorter { class MessageComparer : IComparer where T : IMessageSummary { readonly IList orderBy; public MessageComparer (IList orderBy) { this.orderBy = orderBy; } #region IComparer implementation static int CompareDisplayNames (InternetAddressList list1, InternetAddressList list2) { var m1 = list1.Mailboxes.GetEnumerator (); var m2 = list2.Mailboxes.GetEnumerator (); bool n1 = m1.MoveNext (); bool n2 = m2.MoveNext (); while (n1 && n2) { var name1 = m1.Current.Name ?? string.Empty; var name2 = m2.Current.Name ?? string.Empty; int cmp; if ((cmp = string.Compare (name1, name2, StringComparison.OrdinalIgnoreCase)) != 0) return cmp; n1 = m1.MoveNext (); n2 = m2.MoveNext (); } return n1 ? 1 : (n2 ? -1 : 0); } static int CompareMailboxAddresses (InternetAddressList list1, InternetAddressList list2) { var m1 = list1.Mailboxes.GetEnumerator (); var m2 = list2.Mailboxes.GetEnumerator (); bool n1 = m1.MoveNext (); bool n2 = m2.MoveNext (); while (n1 && n2) { int cmp; if ((cmp = string.Compare (m1.Current.Address, m2.Current.Address, StringComparison.OrdinalIgnoreCase)) != 0) return cmp; n1 = m1.MoveNext (); n2 = m2.MoveNext (); } return n1 ? 1 : (n2 ? -1 : 0); } public int Compare (T x, T y) { int cmp = 0; for (int i = 0; i < orderBy.Count; i++) { switch (orderBy[i].Type) { case OrderByType.Annotation: var annotation = (OrderByAnnotation) orderBy[i]; var xannotation = x.Annotations?.FirstOrDefault (a => a.Entry == annotation.Entry); var yannotation = y.Annotations?.FirstOrDefault (a => a.Entry == annotation.Entry); var xvalue = xannotation?.Properties[annotation.Attribute] ?? string.Empty; var yvalue = yannotation?.Properties[annotation.Attribute] ?? string.Empty; cmp = string.Compare (xvalue, yvalue, StringComparison.OrdinalIgnoreCase); break; case OrderByType.Arrival: cmp = x.Index.CompareTo (y.Index); break; case OrderByType.Cc: cmp = CompareMailboxAddresses (x.Envelope.Cc, y.Envelope.Cc); break; case OrderByType.Date: cmp = x.Date.CompareTo (y.Date); break; case OrderByType.DisplayFrom: cmp = CompareDisplayNames (x.Envelope.From, y.Envelope.From); break; case OrderByType.From: cmp = CompareMailboxAddresses (x.Envelope.From, y.Envelope.From); break; case OrderByType.ModSeq: var xmodseq = x.ModSeq ?? 0; var ymodseq = y.ModSeq ?? 0; cmp = xmodseq.CompareTo (ymodseq); break; case OrderByType.Size: var xsize = x.Size ?? 0; var ysize = y.Size ?? 0; cmp = xsize.CompareTo (ysize); break; case OrderByType.Subject: var xsubject = x.Envelope.Subject ?? string.Empty; var ysubject = y.Envelope.Subject ?? string.Empty; cmp = string.Compare (xsubject, ysubject, StringComparison.OrdinalIgnoreCase); break; case OrderByType.DisplayTo: cmp = CompareDisplayNames (x.Envelope.To, y.Envelope.To); break; case OrderByType.To: cmp = CompareMailboxAddresses (x.Envelope.To, y.Envelope.To); break; } if (cmp == 0) continue; return orderBy[i].Order == SortOrder.Descending ? cmp * -1 : cmp; } return cmp; } #endregion } static MessageSummaryItems GetMessageSummaryItems (IList orderBy) { var items = MessageSummaryItems.None; for (int i = 0; i < orderBy.Count; i++) { switch (orderBy[i].Type) { case OrderByType.Annotation: items |= MessageSummaryItems.Annotations; break; case OrderByType.Arrival: break; case OrderByType.Cc: case OrderByType.Date: case OrderByType.DisplayFrom: case OrderByType.DisplayTo: case OrderByType.From: case OrderByType.Subject: case OrderByType.To: items |= MessageSummaryItems.Envelope; break; case OrderByType.ModSeq: items |= MessageSummaryItems.ModSeq; break; case OrderByType.Size: items |= MessageSummaryItems.Size; break; } } return items; } /// /// Sorts the messages by the specified ordering. /// /// /// Sorts the messages by the specified ordering. /// /// The sorted messages. /// The message items must implement the interface. /// The messages to sort. /// The sort ordering. /// /// is null. /// -or- /// is null. /// /// /// contains one or more items that is missing information needed for sorting. /// -or- /// is an empty list. /// public static IList Sort (this IEnumerable messages, IList orderBy) where T : IMessageSummary { if (messages == null) throw new ArgumentNullException (nameof (messages)); if (orderBy == null) throw new ArgumentNullException (nameof (orderBy)); if (orderBy.Count == 0) throw new ArgumentException ("No sort order provided.", nameof (orderBy)); var requiredFields = GetMessageSummaryItems (orderBy); var list = new List (); foreach (var message in messages) { if ((message.Fields & requiredFields) != requiredFields) throw new ArgumentException ("One or more messages is missing information needed for sorting.", nameof (messages)); list.Add (message); } if (list.Count < 2) return list; var comparer = new MessageComparer (orderBy); list.Sort (comparer); return list; } /// /// Sorts the messages by the specified ordering. /// /// /// Sorts the messages by the specified ordering. /// /// The sorted messages. /// The message items must implement the interface. /// The messages to sort. /// The sort ordering. /// /// is null. /// -or- /// is null. /// /// /// contains one or more items that is missing information needed for sorting. /// -or- /// is an empty list. /// public static void Sort (this List messages, IList orderBy) where T : IMessageSummary { if (messages == null) throw new ArgumentNullException (nameof (messages)); if (orderBy == null) throw new ArgumentNullException (nameof (orderBy)); if (orderBy.Count == 0) throw new ArgumentException ("No sort order provided.", nameof (orderBy)); var requiredFields = GetMessageSummaryItems (orderBy); for (int i = 0; i < messages.Count; i++) { if ((messages[i].Fields & requiredFields) != requiredFields) throw new ArgumentException ("One or more messages is missing information needed for sorting.", nameof (messages)); } if (messages.Count < 2) return; var comparer = new MessageComparer (orderBy); messages.Sort (comparer); } } }