diff --git a/example/example.dart b/example/example.dart index 0a5e9eea..010da4df 100644 --- a/example/example.dart +++ b/example/example.dart @@ -37,7 +37,10 @@ void main() async { await smtpClient.ehlo(); if (smtpClient.serverInfo.supportsAuth(AuthMechanism.plain)) { await smtpClient.authenticate( - 'user@example.com', 'password', AuthMechanism.plain); + 'user@example.com', + 'password', + AuthMechanism.plain, + ); } final builder = MessageBuilder() diff --git a/lib/src/codecs/mail_codec.dart b/lib/src/codecs/mail_codec.dart index 9eceeb6a..539b4d1e 100644 --- a/lib/src/codecs/mail_codec.dart +++ b/lib/src/codecs/mail_codec.dart @@ -410,16 +410,20 @@ abstract class MailCodec { return text; } final buffer = StringBuffer(); - final runes = text.runes; + final runes = text.runes.toList(growable: false); int? lastRune; int? lastSpaceIndex; var currentLineLength = 0; var currentLineStartIndex = 0; for (var runeIndex = 0; runeIndex < runes.length; runeIndex++) { - final rune = runes.elementAt(runeIndex); + final rune = runes[runeIndex]; if (rune == AsciiRunes.runeLineFeed && lastRune == AsciiRunes.runeCarriageReturn) { - buffer.write(text.substring(currentLineStartIndex, runeIndex + 1)); + buffer.write( + String.fromCharCodes( + runes.sublist(currentLineStartIndex, runeIndex + 1), + ), + ); currentLineLength = 0; currentLineStartIndex = runeIndex + 1; lastSpaceIndex = null; @@ -433,7 +437,7 @@ abstract class MailCodec { // edge case: this could be in the middle of a \r\n sequence: if (rune == AsciiRunes.runeCarriageReturn && runeIndex < runes.length - 1 && - runes.elementAt(runeIndex + 1) == AsciiRunes.runeLineFeed) { + runes[runeIndex + 1] == AsciiRunes.runeLineFeed) { lastRune = rune; continue; // the break will be handled in the next loop iteration } @@ -444,7 +448,11 @@ abstract class MailCodec { endIndex++; } buffer - ..write(text.substring(currentLineStartIndex, endIndex)) + ..write( + String.fromCharCodes( + runes.sublist(currentLineStartIndex, endIndex), + ), + ) ..write('\r\n'); currentLineLength = 0; currentLineStartIndex = endIndex; @@ -454,8 +462,8 @@ abstract class MailCodec { lastRune = rune; } - if (currentLineStartIndex < text.length) { - buffer.write(text.substring(currentLineStartIndex)); + if (currentLineStartIndex < runes.length) { + buffer.write(String.fromCharCodes(runes.sublist(currentLineStartIndex))); } return buffer.toString(); diff --git a/lib/src/imap/imap_client.dart b/lib/src/imap/imap_client.dart index 139f3e26..2d90f3ec 100644 --- a/lib/src/imap/imap_client.dart +++ b/lib/src/imap/imap_client.dart @@ -345,7 +345,7 @@ class ImapClient extends ClientBase { // the continuation would hang forever since onConnectionError is not // invoked on an expected disconnect. _failPendingIdleContinuation('client disconnected'); - _eventController.close(); + await _eventController.close(); return super.disconnect(); } @@ -2762,7 +2762,8 @@ class ImapClient extends ClientBase { } if (_isInIdleMode) { // `+ idling` from the server -- IDLE mode is now truly active. - // Resolve any pending completer from idleStart(waitForContinuation: true). + // Resolve any pending completer from + // idleStart(waitForContinuation: true). final completer = _idleContinuationCompleter; if (completer != null && !completer.isCompleted) { _idleContinuationCompleter = null; diff --git a/lib/src/message_builder.dart b/lib/src/message_builder.dart index 906f1962..90bc1db8 100644 --- a/lib/src/message_builder.dart +++ b/lib/src/message_builder.dart @@ -1437,10 +1437,13 @@ class MessageBuilder extends PartBuilder { 'Return-Receipt-To', ); if (recipient == null || recipient.isEmpty) { + final headerValue = originalMessage.getHeaderValue( + MailConventions.headerDispositionNotificationTo, + ); throw InvalidArgumentException( - 'Invalid header ${MailConventions.headerDispositionNotificationTo} ' - 'in message: ' - '${originalMessage.getHeaderValue(MailConventions.headerDispositionNotificationTo)}', + 'Invalid header ' + '${MailConventions.headerDispositionNotificationTo} in message: ' + '$headerValue', ); } } @@ -1503,7 +1506,9 @@ class MessageBuilder extends PartBuilder { return '>\r\n'; } - return '>${header.split('\r\n').join('\r\n>')}\r\n>${text.split('\r\n').join('\r\n>')}'; + final quotedHeader = header.split('\r\n').join('\r\n>'); + final quotedText = text.split('\r\n').join('\r\n>'); + return '>$quotedHeader\r\n>$quotedText'; } /// Generates a message ID diff --git a/lib/src/mime_message.dart b/lib/src/mime_message.dart index 36c0b6a0..22ac2d71 100644 --- a/lib/src/mime_message.dart +++ b/lib/src/mime_message.dart @@ -614,9 +614,14 @@ class MimeMessage extends MimePart { this.guid = guid; } - int? xGmThrid; // X-GM-THRID - int? xGmMsgid; // X-GM-MSGID - List? xGmLabels; // X-GM-LABELS + /// The Gmail thread ID (X-GM-THRID) + int? xGmThrid; + + /// The Gmail message ID (X-GM-MSGID) + int? xGmMsgid; + + /// The Gmail labels (X-GM-LABELS) + List? xGmLabels; /// The modifications sequence of this message. /// diff --git a/lib/src/smtp/smtp_client.dart b/lib/src/smtp/smtp_client.dart index 304d13f8..a28d79ae 100644 --- a/lib/src/smtp/smtp_client.dart +++ b/lib/src/smtp/smtp_client.dart @@ -122,7 +122,9 @@ class SmtpClient extends ClientBase { /// /// Usage: /// ```dart - /// smtpClient.eventStream.whereType().listen((event) { + /// smtpClient.eventStream + /// .whereType() + /// .listen((event) { /// _log(event.type); /// }); /// ``` diff --git a/test/message_builder_test.dart b/test/message_builder_test.dart index 8c05fc43..04b2f56c 100644 --- a/test/message_builder_test.dart +++ b/test/message_builder_test.dart @@ -1748,6 +1748,35 @@ END:VCARD\r //expect(part?.decodeDispositionNotification()) }); }); + + group('performance', () { + test('buildMimeMessage should be fast with 100KB body', () { + final builder = MessageBuilder.prepareMultipartAlternativeMessage() + ..from = [const MailAddress('Sender', 'sender@example.com')] + ..to = [const MailAddress('Recipient', 'recipient@example.com')] + ..subject = 'Large body perf test'; + + final largeBody = 'A' * (100 * 1024); + + builder.addTextPlain(largeBody); + + final sw = Stopwatch()..start(); + final mime = builder.buildMimeMessage(); + sw.stop(); + + // Sanity checks + expect(mime, isNotNull); + expect(mime.renderMessage().length, greaterThan(100000)); + + expect( + sw.elapsedMilliseconds, + lessThan(1000), + reason: + 'buildMimeMessage() should complete in <1s, but currently ' + 'takes ${sw.elapsedMilliseconds}ms for a 100KB body. ', + ); + }); + }); } const String complexMessageText = '''