Compare commits

..

No commits in common. "d7723615fe311a2fc8028946fd27c091848d788d" and "9223a7d4032deb54d144e3f49d19788db3218da2" have entirely different histories.

5 changed files with 41 additions and 163 deletions

View File

@ -1,7 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:moxlib/moxlib.dart';
import 'package:moxxmpp/src/buffer.dart'; import 'package:moxxmpp/src/buffer.dart';
import 'package:moxxmpp/src/errors.dart'; import 'package:moxxmpp/src/errors.dart';
import 'package:moxxmpp/src/events.dart'; import 'package:moxxmpp/src/events.dart';
@ -73,23 +72,6 @@ class XmppConnectionResult {
final XmppError? error; final XmppError? error;
} }
@immutable
class _StanzaAwaitableData {
const _StanzaAwaitableData(this.sentTo, this.id);
final String sentTo;
final String id;
@override
int get hashCode => sentTo.hashCode ^ id.hashCode;
@override
bool operator==(Object other) {
return other is _StanzaAwaitableData &&
other.sentTo == sentTo &&
other.id == id;
}
}
class XmppConnection { class XmppConnection {
/// [_socket] is for debugging purposes. /// [_socket] is for debugging purposes.
/// [connectionPingDuration] is the duration after which a ping will be sent to keep /// [connectionPingDuration] is the duration after which a ping will be sent to keep
@ -146,7 +128,7 @@ class XmppConnection {
/// A policy on how to reconnect /// A policy on how to reconnect
final ReconnectionPolicy _reconnectionPolicy; final ReconnectionPolicy _reconnectionPolicy;
/// A list of stanzas we are tracking with its corresponding critical section /// A list of stanzas we are tracking with its corresponding critical section
final Map<_StanzaAwaitableData, Completer<XMLNode>> _awaitingResponse; final Map<String, Completer<XMLNode>> _awaitingResponse;
final Lock _awaitingResponseLock; final Lock _awaitingResponseLock;
/// Helpers /// Helpers
@ -446,10 +428,9 @@ class XmppConnection {
/// none. /// none.
// TODO(Unknown): if addId = false, the function crashes. // TODO(Unknown): if addId = false, the function crashes.
Future<XMLNode> sendStanza(Stanza stanza, { StanzaFromType addFrom = StanzaFromType.full, bool addId = true, bool awaitable = true, bool encrypted = false }) async { Future<XMLNode> sendStanza(Stanza stanza, { StanzaFromType addFrom = StanzaFromType.full, bool addId = true, bool awaitable = true, bool encrypted = false }) async {
assert(implies(addId == false && stanza.id == null, !awaitable), 'Cannot await a stanza with no id');
// Add extra data in case it was not set
var stanza_ = stanza; var stanza_ = stanza;
// Add extra data in case it was not set
if (addId && (stanza_.id == null || stanza_.id == '')) { if (addId && (stanza_.id == null || stanza_.id == '')) {
stanza_ = stanza.copyWith(id: generateId()); stanza_ = stanza.copyWith(id: generateId());
} }
@ -467,6 +448,8 @@ class XmppConnection {
} }
} }
final id = stanza_.id!;
_log.fine('Running pre stanza handlers..'); _log.fine('Running pre stanza handlers..');
final data = await _runOutgoingPreStanzaHandlers( final data = await _runOutgoingPreStanzaHandlers(
stanza_, stanza_,
@ -504,45 +487,42 @@ class XmppConnection {
final stanzaString = data.stanza.toXml(); final stanzaString = data.stanza.toXml();
// ignore: cascade_invocations // ignore: cascade_invocations
_log.fine('Attempting to acquire lock for ${data.stanza.id}...'); _log.fine('Attempting to acquire lock for $id...');
// TODO(PapaTutuWawa): Handle this much more graceful // TODO(PapaTutuWawa): Handle this much more graceful
var future = Future.value(XMLNode(tag: 'not-used')); var future = Future.value(XMLNode(tag: 'not-used'));
await _awaitingResponseLock.synchronized(() async { await _awaitingResponseLock.synchronized(() async {
_log.fine('Lock acquired for ${data.stanza.id}'); _log.fine('Lock acquired for $id');
if (awaitable) {
_awaitingResponse[id] = Completer();
}
_StanzaAwaitableData? key; // This uses the StreamManager to behave like a send queue
if (awaitable) { if (await _canSendData()) {
key = _StanzaAwaitableData(data.stanza.to!, data.stanza.id!); _socket.write(stanzaString);
_awaitingResponse[key] = Completer();
}
// This uses the StreamManager to behave like a send queue // Try to ack every stanza
if (await _canSendData()) { // NOTE: Here we have send an Ack request nonza. This is now done by StreamManagementManager when receiving the StanzaSentEvent
_socket.write(stanzaString); } else {
_log.fine('_canSendData() returned false.');
}
// Try to ack every stanza _log.fine('Running post stanza handlers..');
// NOTE: Here we have send an Ack request nonza. This is now done by StreamManagementManager when receiving the StanzaSentEvent await _runOutgoingPostStanzaHandlers(
} else {
_log.fine('_canSendData() returned false.');
}
_log.fine('Running post stanza handlers..');
await _runOutgoingPostStanzaHandlers(
stanza_,
initial: StanzaHandlerData(
false,
false,
null,
stanza_, stanza_,
), initial: StanzaHandlerData(
); false,
_log.fine('Done'); false,
null,
stanza_,
),
);
_log.fine('Done');
if (awaitable) { if (awaitable) {
future = _awaitingResponse[key]!.future; future = _awaitingResponse[id]!.future;
} }
_log.fine('Releasing lock for ${data.stanza.id}'); _log.fine('Releasing lock for $id');
}); });
return future; return future;
@ -711,14 +691,10 @@ class XmppConnection {
final id = incomingPreHandlers.stanza.attributes['id'] as String?; final id = incomingPreHandlers.stanza.attributes['id'] as String?;
var awaited = false; var awaited = false;
await _awaitingResponseLock.synchronized(() async { await _awaitingResponseLock.synchronized(() async {
if (id != null && incomingPreHandlers.stanza.from != null) { if (id != null && _awaitingResponse.containsKey(id)) {
final key = _StanzaAwaitableData(incomingPreHandlers.stanza.from!, id); _awaitingResponse[id]!.complete(incomingPreHandlers.stanza);
final comp = _awaitingResponse[key]; _awaitingResponse.remove(id);
if (comp != null) { awaited = true;
comp.complete(incomingPreHandlers.stanza);
_awaitingResponse.remove(key);
awaited = true;
}
} }
}); });

View File

@ -1,4 +1,3 @@
import 'package:moxlib/moxlib.dart';
import 'package:moxxmpp/src/events.dart'; import 'package:moxxmpp/src/events.dart';
import 'package:moxxmpp/src/jid.dart'; import 'package:moxxmpp/src/jid.dart';
import 'package:moxxmpp/src/managers/base.dart'; import 'package:moxxmpp/src/managers/base.dart';
@ -21,7 +20,6 @@ import 'package:moxxmpp/src/xeps/xep_0444.dart';
import 'package:moxxmpp/src/xeps/xep_0446.dart'; import 'package:moxxmpp/src/xeps/xep_0446.dart';
import 'package:moxxmpp/src/xeps/xep_0447.dart'; import 'package:moxxmpp/src/xeps/xep_0447.dart';
import 'package:moxxmpp/src/xeps/xep_0448.dart'; import 'package:moxxmpp/src/xeps/xep_0448.dart';
import 'package:moxxmpp/src/xeps/xep_0461.dart';
/// Data used to build a message stanza. /// Data used to build a message stanza.
/// ///
@ -142,11 +140,6 @@ class MessageManager extends XmppManagerBase {
/// element to this id. If originId is non-null, then it will create an "origin-id" /// element to this id. If originId is non-null, then it will create an "origin-id"
/// child in the message stanza and set its id to originId. /// child in the message stanza and set its id to originId.
void sendMessage(MessageDetails details) { void sendMessage(MessageDetails details) {
assert(
implies(details.quoteBody != null, details.quoteFrom != null && details.quoteId != null),
'When quoting a message, then quoteFrom and quoteId must also be non-null',
);
final stanza = Stanza.message( final stanza = Stanza.message(
to: details.to, to: details.to,
type: 'chat', type: 'chat',
@ -155,11 +148,11 @@ class MessageManager extends XmppManagerBase {
); );
if (details.quoteBody != null) { if (details.quoteBody != null) {
final quote = QuoteData.fromBodies(details.quoteBody!, details.body!); final fallback = '&gt; ${details.quoteBody!}';
stanza stanza
..addChild( ..addChild(
XMLNode(tag: 'body', text: quote.body), XMLNode(tag: 'body', text: '$fallback\n${details.body}'),
) )
..addChild( ..addChild(
XMLNode.xmlns( XMLNode.xmlns(
@ -183,7 +176,7 @@ class MessageManager extends XmppManagerBase {
tag: 'body', tag: 'body',
attributes: <String, String>{ attributes: <String, String>{
'start': '0', 'start': '0',
'end': '${quote.fallbackLength}', 'end': '${fallback.length}'
}, },
) )
], ],

View File

@ -1,4 +1,3 @@
import 'package:meta/meta.dart';
import 'package:moxxmpp/src/managers/base.dart'; import 'package:moxxmpp/src/managers/base.dart';
import 'package:moxxmpp/src/managers/data.dart'; import 'package:moxxmpp/src/managers/data.dart';
import 'package:moxxmpp/src/managers/handlers.dart'; import 'package:moxxmpp/src/managers/handlers.dart';
@ -6,7 +5,6 @@ import 'package:moxxmpp/src/managers/namespaces.dart';
import 'package:moxxmpp/src/namespaces.dart'; import 'package:moxxmpp/src/namespaces.dart';
import 'package:moxxmpp/src/stanza.dart'; import 'package:moxxmpp/src/stanza.dart';
/// Data summarizing the XEP-0461 data.
class ReplyData { class ReplyData {
const ReplyData({ const ReplyData({
required this.to, required this.to,
@ -14,57 +12,12 @@ class ReplyData {
this.start, this.start,
this.end, this.end,
}); });
/// The bare JID to whom the reply applies to
final String to; final String to;
/// The stanza ID of the message that is replied to
final String id; final String id;
/// The start of the fallback body (inclusive)
final int? start; final int? start;
/// The end of the fallback body (exclusive)
final int? end; final int? end;
/// Applies the metadata to the received body [body] in order to remove the fallback.
/// If either [ReplyData.start] or [ReplyData.end] are null, then body is returned as
/// is.
String removeFallback(String body) {
if (start == null || end == null) return body;
return body.replaceRange(start!, end, '');
}
} }
/// Internal class describing how to build a message with a quote fallback body.
@visibleForTesting
class QuoteData {
const QuoteData(this.body, this.fallbackLength);
/// Takes the body of the message we want to quote [quoteBody] and the content of
/// the reply [body] and computes the fallback body and its length.
factory QuoteData.fromBodies(String quoteBody, String body) {
final fallback = quoteBody
.split('\n')
.map((line) => '> $line\n')
.join();
return QuoteData(
'$fallback$body',
fallback.length,
);
}
/// The new body with fallback data at the beginning
final String body;
/// The length of the fallback data
final int fallbackLength;
}
/// A manager implementing support for parsing XEP-0461 metadata. The
/// MessageRepliesManager itself does not modify the body of the message.
class MessageRepliesManager extends XmppManagerBase { class MessageRepliesManager extends XmppManagerBase {
@override @override
String getName() => 'MessageRepliesManager'; String getName() => 'MessageRepliesManager';

View File

@ -348,7 +348,7 @@ void main() {
), ),
StanzaExpectation( StanzaExpectation(
"<iq to='user@example.com' type='get' id='a' xmlns='jabber:client' />", "<iq to='user@example.com' type='get' id='a' xmlns='jabber:client' />",
"<iq from='user@example.com' type='result' id='a' />", "<iq to='user@example.com' type='result' id='a' />",
ignoreId: true, ignoreId: true,
adjustId: true, adjustId: true,
), ),

View File

@ -1,44 +0,0 @@
import 'package:moxxmpp/moxxmpp.dart';
import 'package:test/test.dart';
void main() {
test('Test building a singleline quote', () {
final quote = QuoteData.fromBodies('Hallo Welt', 'Hello Earth!');
expect(quote.body, '> Hallo Welt\nHello Earth!');
expect(quote.fallbackLength, 13);
});
test('Test building a multiline quote', () {
final quote = QuoteData.fromBodies('Hallo Welt\nHallo Erde', 'How are you?');
expect(quote.body, '> Hallo Welt\n> Hallo Erde\nHow are you?');
expect(quote.fallbackLength, 26);
});
test('Applying a singleline quote', () {
final body = '> Hallo Welt\nHello right back!';
final reply = ReplyData(
to: '',
id: '',
start: 0,
end: 13,
);
final bodyWithoutFallback = reply.removeFallback(body);
expect(bodyWithoutFallback, 'Hello right back!');
});
test('Applying a multiline quote', () {
final body = "> Hallo Welt\n> How are you?\nI'm fine.\nThank you!";
final reply = ReplyData(
to: '',
id: '',
start: 0,
end: 28,
);
final bodyWithoutFallback = reply.removeFallback(body);
expect(bodyWithoutFallback, "I'm fine.\nThank you!");
});
}