Compare commits

..

No commits in common. "6da3342f22f95140ed2220391b1597280dc3bf42" and "7e588f01b0e226a0c6b03f66d5f2b1d055c8715f" have entirely different histories.

37 changed files with 271 additions and 272 deletions

View File

@ -226,7 +226,7 @@ class XmppConnection {
/// Registers a list of [XmppManagerBase] sub-classes as managers on this connection. /// Registers a list of [XmppManagerBase] sub-classes as managers on this connection.
Future<void> registerManagers(List<XmppManagerBase> managers) async { Future<void> registerManagers(List<XmppManagerBase> managers) async {
for (final manager in managers) { for (final manager in managers) {
_log.finest('Registering ${manager.id}'); _log.finest('Registering ${manager.getId()}');
manager.register( manager.register(
XmppManagerAttributes( XmppManagerAttributes(
sendStanza: sendStanza, sendStanza: sendStanza,
@ -242,7 +242,8 @@ class XmppConnection {
), ),
); );
_xmppManagers[manager.id] = manager; final id = manager.getId();
_xmppManagers[id] = manager;
_incomingStanzaHandlers.addAll(manager.getIncomingStanzaHandlers()); _incomingStanzaHandlers.addAll(manager.getIncomingStanzaHandlers());
_incomingPreStanzaHandlers.addAll(manager.getIncomingPreStanzaHandlers()); _incomingPreStanzaHandlers.addAll(manager.getIncomingPreStanzaHandlers());
@ -259,7 +260,7 @@ class XmppConnection {
// Run the post register callbacks // Run the post register callbacks
for (final manager in _xmppManagers.values) { for (final manager in _xmppManagers.values) {
if (!manager.initialized) { if (!manager.initialized) {
_log.finest('Running post-registration callback for ${manager.name}'); _log.finest('Running post-registration callback for ${manager.getName()}');
await manager.postRegisterCallback(); await manager.postRegisterCallback();
} }
} }

View File

@ -10,8 +10,6 @@ import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart'; import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
abstract class XmppManagerBase { abstract class XmppManagerBase {
XmppManagerBase(this.id);
late final XmppManagerAttributes _managerAttributes; late final XmppManagerAttributes _managerAttributes;
late final Logger _log; late final Logger _log;
@ -21,7 +19,7 @@ abstract class XmppManagerBase {
/// Registers the callbacks from XmppConnection with the manager /// Registers the callbacks from XmppConnection with the manager
void register(XmppManagerAttributes attributes) { void register(XmppManagerAttributes attributes) {
_managerAttributes = attributes; _managerAttributes = attributes;
_log = Logger(name); _log = Logger(getName());
} }
/// Returns the attributes that are registered with the manager. /// Returns the attributes that are registered with the manager.
@ -62,11 +60,11 @@ abstract class XmppManagerBase {
List<Identity> getDiscoIdentities() => []; List<Identity> getDiscoIdentities() => [];
/// Return the Id (akin to xmlns) of this manager. /// Return the Id (akin to xmlns) of this manager.
final String id; String getId();
/// Return a name that will be used for logging.
String getName();
/// The name of the manager.
String get name => toString();
/// Return the logger for this manager. /// Return the logger for this manager.
Logger get logger => _log; Logger get logger => _log;

View File

@ -76,7 +76,11 @@ class MessageDetails {
} }
class MessageManager extends XmppManagerBase { class MessageManager extends XmppManagerBase {
MessageManager() : super(messageManager); @override
String getId() => messageManager;
@override
String getName() => 'MessageManager';
@override @override
List<StanzaHandler> getIncomingStanzaHandlers() => [ List<StanzaHandler> getIncomingStanzaHandlers() => [

View File

@ -4,7 +4,11 @@ import 'package:moxxmpp/src/managers/namespaces.dart';
import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart'; import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart';
class PingManager extends XmppManagerBase { class PingManager extends XmppManagerBase {
PingManager() : super(pingManager); @override
String getId() => pingManager;
@override
String getName() => 'PingManager';
@override @override
Future<bool> isSupported() async => true; Future<bool> isSupported() async => true;

View File

@ -17,10 +17,16 @@ typedef PresencePreSendCallback = Future<List<XMLNode>> Function();
/// A mandatory manager that handles initial presence sending, sending of subscription /// A mandatory manager that handles initial presence sending, sending of subscription
/// request management requests and triggers events for incoming presence stanzas. /// request management requests and triggers events for incoming presence stanzas.
class PresenceManager extends XmppManagerBase { class PresenceManager extends XmppManagerBase {
PresenceManager() : super(presenceManager); PresenceManager() : super();
/// The list of pre-send callbacks. /// The list of pre-send callbacks.
final List<PresencePreSendCallback> _presenceCallbacks = List.empty(growable: true); final List<PresencePreSendCallback> _presenceCallbacks = List.empty(growable: true);
@override
String getId() => presenceManager;
@override
String getName() => 'PresenceManager';
@override @override
List<StanzaHandler> getIncomingStanzaHandlers() => [ List<StanzaHandler> getIncomingStanzaHandlers() => [

View File

@ -93,7 +93,7 @@ class RosterFeatureNegotiator extends XmppFeatureNegotiatorBase {
/// This manager requires a RosterFeatureNegotiator to be registered. /// This manager requires a RosterFeatureNegotiator to be registered.
class RosterManager extends XmppManagerBase { class RosterManager extends XmppManagerBase {
RosterManager(this._stateManager) : super(rosterManager); RosterManager(this._stateManager) : super();
/// The class managing the entire roster state. /// The class managing the entire roster state.
final BaseRosterStateManager _stateManager; final BaseRosterStateManager _stateManager;
@ -104,6 +104,12 @@ class RosterManager extends XmppManagerBase {
_stateManager.register(attributes.sendEvent); _stateManager.register(attributes.sendEvent);
} }
@override
String getId() => rosterManager;
@override
String getName() => 'RosterManager';
@override @override
List<StanzaHandler> getIncomingStanzaHandlers() => [ List<StanzaHandler> getIncomingStanzaHandlers() => [
StanzaHandler( StanzaHandler(

View File

@ -1,67 +0,0 @@
import 'dart:async';
import 'package:meta/meta.dart';
import 'package:synchronized/synchronized.dart';
/// This class allows for multiple asynchronous code places to wait on the
/// same computation of type [V], indentified by a key of type [K].
class WaitForTracker<K, V> {
/// The mapping of key -> Completer for the pending tasks.
final Map<K, List<Completer<V>>> _tracker = {};
/// The lock for accessing _tracker.
final Lock _lock = Lock();
/// Wait for a task with key [key]. If there was no such task already
/// present, returns null. If one or more tasks were already present, returns
/// a future that will resolve to the result of the first task.
Future<Future<V>?> waitFor(K key) async {
final result = await _lock.synchronized(() {
if (_tracker.containsKey(key)) {
// The task already exists. Just append outselves
final completer = Completer<V>();
_tracker[key]!.add(completer);
return completer;
}
// The task does not exist yet
_tracker[key] = List<Completer<V>>.empty(growable: true);
return null;
});
return result?.future;
}
/// Resolve a task with key [key] to [value].
Future<void> resolve(K key, V value) async {
await _lock.synchronized(() {
if (!_tracker.containsKey(key)) return;
for (final completer in _tracker[key]!) {
completer.complete(value);
}
_tracker.remove(key);
});
}
Future<void> resolveAll(V value) async {
await _lock.synchronized(() {
for (final key in _tracker.keys) {
for (final completer in _tracker[key]!) {
completer.complete(value);
}
}
});
}
/// Remove all tasks from the tracker.
Future<void> clear() async {
await _lock.synchronized(_tracker.clear);
}
@visibleForTesting
bool hasTasksRunning() => _tracker.isNotEmpty;
@visibleForTesting
List<Completer<V>> getRunningTasks(K key) => _tracker[key]!;
}

View File

@ -11,7 +11,13 @@ import 'package:moxxmpp/src/xeps/xep_0446.dart';
const fileUploadNotificationXmlns = 'proto:urn:xmpp:fun:0'; const fileUploadNotificationXmlns = 'proto:urn:xmpp:fun:0';
class FileUploadNotificationManager extends XmppManagerBase { class FileUploadNotificationManager extends XmppManagerBase {
FileUploadNotificationManager() : super(fileUploadNotificationManager); FileUploadNotificationManager() : super();
@override
String getId() => fileUploadNotificationManager;
@override
String getName() => 'FileUploadNotificationManager';
@override @override
List<StanzaHandler> getIncomingStanzaHandlers() => [ List<StanzaHandler> getIncomingStanzaHandlers() => [

View File

@ -1,23 +0,0 @@
import 'package:meta/meta.dart';
@internal
@immutable
class DiscoCacheKey {
const DiscoCacheKey(this.jid, this.node);
/// The JID we're requesting disco data from.
final String jid;
/// Optionally the node we are requesting from.
final String? node;
@override
bool operator ==(Object other) {
return other is DiscoCacheKey &&
jid == other.jid &&
node == other.node;
}
@override
int get hashCode => jid.hashCode ^ node.hashCode;
}

View File

@ -1,6 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:moxxmpp/src/connection.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';
@ -11,8 +10,6 @@ import 'package:moxxmpp/src/namespaces.dart';
import 'package:moxxmpp/src/stanza.dart'; import 'package:moxxmpp/src/stanza.dart';
import 'package:moxxmpp/src/stringxml.dart'; import 'package:moxxmpp/src/stringxml.dart';
import 'package:moxxmpp/src/types/result.dart'; import 'package:moxxmpp/src/types/result.dart';
import 'package:moxxmpp/src/util/wait.dart';
import 'package:moxxmpp/src/xeps/xep_0030/cache.dart';
import 'package:moxxmpp/src/xeps/xep_0030/errors.dart'; import 'package:moxxmpp/src/xeps/xep_0030/errors.dart';
import 'package:moxxmpp/src/xeps/xep_0030/helpers.dart'; import 'package:moxxmpp/src/xeps/xep_0030/helpers.dart';
import 'package:moxxmpp/src/xeps/xep_0030/types.dart'; import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
@ -25,6 +22,21 @@ typedef DiscoInfoRequestCallback = Future<DiscoInfo> Function();
/// Callback that is called when a disco#items requests is received on a given node. /// Callback that is called when a disco#items requests is received on a given node.
typedef DiscoItemsRequestCallback = Future<List<DiscoItem>> Function(); typedef DiscoItemsRequestCallback = Future<List<DiscoItem>> Function();
@immutable
class DiscoCacheKey {
const DiscoCacheKey(this.jid, this.node);
final String jid;
final String? node;
@override
bool operator ==(Object other) {
return other is DiscoCacheKey && jid == other.jid && node == other.node;
}
@override
int get hashCode => jid.hashCode ^ node.hashCode;
}
/// This manager implements XEP-0030 by providing a way of performing disco#info and /// This manager implements XEP-0030 by providing a way of performing disco#info and
/// disco#items requests and answering those requests. /// disco#items requests and answering those requests.
/// A caching mechanism is also provided. /// A caching mechanism is also provided.
@ -33,7 +45,7 @@ class DiscoManager extends XmppManagerBase {
/// to a disco#info response. /// to a disco#info response.
DiscoManager(List<Identity> identities) DiscoManager(List<Identity> identities)
: _identities = List<Identity>.from(identities), : _identities = List<Identity>.from(identities),
super(discoManager); super();
/// Our features /// Our features
final List<String> _features = List.empty(growable: true); final List<String> _features = List.empty(growable: true);
@ -50,11 +62,8 @@ class DiscoManager extends XmppManagerBase {
/// Map full JID to Disco Info /// Map full JID to Disco Info
final Map<DiscoCacheKey, DiscoInfo> _discoInfoCache = {}; final Map<DiscoCacheKey, DiscoInfo> _discoInfoCache = {};
/// The tracker for tracking disco#info queries that are in flight. /// Mapping the full JID to a list of running requests
final WaitForTracker<DiscoCacheKey, Result<DiscoError, DiscoInfo>> _discoInfoTracker = WaitForTracker(); final Map<DiscoCacheKey, List<Completer<Result<DiscoError, DiscoInfo>>>> _runningInfoQueries = {};
/// The tracker for tracking disco#info queries that are in flight.
final WaitForTracker<DiscoCacheKey, Result<DiscoError, List<DiscoItem>>> _discoItemsTracker = WaitForTracker();
/// Cache lock /// Cache lock
final Lock _cacheLock = Lock(); final Lock _cacheLock = Lock();
@ -70,9 +79,12 @@ class DiscoManager extends XmppManagerBase {
/// The list of disco features that are registered. /// The list of disco features that are registered.
List<String> get features => _features; List<String> get features => _features;
@visibleForTesting
bool hasInfoQueriesRunning() => _runningInfoQueries.isNotEmpty;
@visibleForTesting @visibleForTesting
WaitForTracker<DiscoCacheKey, Result<DiscoError, DiscoInfo>> get infoTracker => _discoInfoTracker; List<Completer<Result<DiscoError, DiscoInfo>>> getRunningInfoQueries(DiscoCacheKey key) => _runningInfoQueries[key]!;
@override @override
List<StanzaHandler> getIncomingStanzaHandlers() => [ List<StanzaHandler> getIncomingStanzaHandlers() => [
@ -90,6 +102,12 @@ class DiscoManager extends XmppManagerBase {
), ),
]; ];
@override
String getId() => discoManager;
@override
String getName() => 'DiscoManager';
@override @override
List<String> getDiscoFeatures() => [ discoInfoXmlns, discoItemsXmlns ]; List<String> getDiscoFeatures() => [ discoInfoXmlns, discoItemsXmlns ];
@ -100,21 +118,7 @@ class DiscoManager extends XmppManagerBase {
Future<void> onXmppEvent(XmppEvent event) async { Future<void> onXmppEvent(XmppEvent event) async {
if (event is PresenceReceivedEvent) { if (event is PresenceReceivedEvent) {
await _onPresence(event.jid, event.presence); await _onPresence(event.jid, event.presence);
} else if (event is ConnectionStateChangedEvent) { } else if (event is StreamResumeFailedEvent) {
// TODO(Unknown): This handling is stupid. We should have an event that is
// triggered when we cannot guarantee that everything is as
// it was before.
if (event.state != XmppConnectionState.connected) return;
if (event.resumed) return;
// Cancel all waiting requests
await _discoInfoTracker.resolveAll(
Result<DiscoError, DiscoInfo>(UnknownDiscoError()),
);
await _discoItemsTracker.resolveAll(
Result<DiscoError, List<DiscoItem>>(UnknownDiscoError()),
);
await _cacheLock.synchronized(() async { await _cacheLock.synchronized(() async {
// Clear the cache // Clear the cache
_discoInfoCache.clear(); _discoInfoCache.clear();
@ -255,37 +259,46 @@ class DiscoManager extends XmppManagerBase {
} }
Future<void> _exitDiscoInfoCriticalSection(DiscoCacheKey key, Result<DiscoError, DiscoInfo> result) async { Future<void> _exitDiscoInfoCriticalSection(DiscoCacheKey key, Result<DiscoError, DiscoInfo> result) async {
await _cacheLock.synchronized(() async { return _cacheLock.synchronized(() async {
// Complete all futures
for (final completer in _runningInfoQueries[key]!) {
completer.complete(result);
}
// Add to cache if it is a result // Add to cache if it is a result
if (result.isType<DiscoInfo>()) { if (result.isType<DiscoInfo>()) {
_discoInfoCache[key] = result.get<DiscoInfo>(); _discoInfoCache[key] = result.get<DiscoInfo>();
} }
// Remove from the request cache
_runningInfoQueries.remove(key);
}); });
await _discoInfoTracker.resolve(key, result);
} }
/// Sends a disco info query to the (full) jid [entity], optionally with node=[node]. /// Sends a disco info query to the (full) jid [entity], optionally with node=[node].
Future<Result<DiscoError, DiscoInfo>> discoInfoQuery(String entity, { String? node, bool shouldEncrypt = true }) async { Future<Result<DiscoError, DiscoInfo>> discoInfoQuery(String entity, { String? node, bool shouldEncrypt = true }) async {
final cacheKey = DiscoCacheKey(entity, node); final cacheKey = DiscoCacheKey(entity, node);
DiscoInfo? info; DiscoInfo? info;
final ffuture = await _cacheLock.synchronized<Future<Future<Result<DiscoError, DiscoInfo>>?>?>(() async { Completer<Result<DiscoError, DiscoInfo>>? completer;
await _cacheLock.synchronized(() async {
// Check if we already know what the JID supports // Check if we already know what the JID supports
if (_discoInfoCache.containsKey(cacheKey)) { if (_discoInfoCache.containsKey(cacheKey)) {
info = _discoInfoCache[cacheKey]; info = _discoInfoCache[cacheKey];
return null;
} else { } else {
return _discoInfoTracker.waitFor(cacheKey); // Is a request running?
if (_runningInfoQueries.containsKey(cacheKey)) {
completer = Completer();
_runningInfoQueries[cacheKey]!.add(completer!);
} else {
_runningInfoQueries[cacheKey] = List.from(<Completer<DiscoInfo?>>[]);
}
} }
}); });
if (info != null) { if (info != null) {
return Result<DiscoError, DiscoInfo>(info); return Result<DiscoError, DiscoInfo>(info);
} else { } else if (completer != null) {
final future = await ffuture; return completer!.future;
if (future != null) {
return future;
}
} }
final stanza = await getAttributes().sendStanza( final stanza = await getAttributes().sendStanza(
@ -318,12 +331,6 @@ class DiscoManager extends XmppManagerBase {
/// Sends a disco items query to the (full) jid [entity], optionally with node=[node]. /// Sends a disco items query to the (full) jid [entity], optionally with node=[node].
Future<Result<DiscoError, List<DiscoItem>>> discoItemsQuery(String entity, { String? node, bool shouldEncrypt = true }) async { Future<Result<DiscoError, List<DiscoItem>>> discoItemsQuery(String entity, { String? node, bool shouldEncrypt = true }) async {
final key = DiscoCacheKey(entity, node);
final future = await _discoItemsTracker.waitFor(key);
if (future != null) {
return future;
}
final stanza = await getAttributes() final stanza = await getAttributes()
.sendStanza( .sendStanza(
buildDiscoItemsQueryStanza(entity, node: node), buildDiscoItemsQueryStanza(entity, node: node),
@ -331,18 +338,12 @@ class DiscoManager extends XmppManagerBase {
) as Stanza; ) as Stanza;
final query = stanza.firstTag('query'); final query = stanza.firstTag('query');
if (query == null) { if (query == null) return Result(InvalidResponseDiscoError());
final result = Result<DiscoError, List<DiscoItem>>(InvalidResponseDiscoError());
await _discoItemsTracker.resolve(key, result);
return result;
}
if (stanza.type == 'error') { if (stanza.type == 'error') {
//final error = stanza.firstTag('error'); //final error = stanza.firstTag('error');
//print("Disco Items error: " + error.toXml()); //print("Disco Items error: " + error.toXml());
final result = Result<DiscoError, List<DiscoItem>>(ErrorResponseDiscoError()); return Result(ErrorResponseDiscoError());
await _discoItemsTracker.resolve(key, result);
return result;
} }
final items = query.findTags('item').map((node) => DiscoItem( final items = query.findTags('item').map((node) => DiscoItem(
@ -351,9 +352,7 @@ class DiscoManager extends XmppManagerBase {
name: node.attributes['name'] as String?, name: node.attributes['name'] as String?,
),).toList(); ),).toList();
final result = Result<DiscoError, List<DiscoItem>>(items); return Result(items);
await _discoItemsTracker.resolve(key, result);
return result;
} }
/// Queries information about a jid based on its node and capability hash. /// Queries information about a jid based on its node and capability hash.

View File

@ -28,9 +28,15 @@ class VCard {
} }
class VCardManager extends XmppManagerBase { class VCardManager extends XmppManagerBase {
VCardManager() : super(vcardManager); VCardManager() : _lastHash = {}, super();
final Map<String, String> _lastHash = {}; final Map<String, String> _lastHash;
@override
String getId() => vcardManager;
@override
String getName() => 'vCardManager';
@override @override
List<StanzaHandler> getIncomingStanzaHandlers() => [ List<StanzaHandler> getIncomingStanzaHandlers() => [
StanzaHandler( StanzaHandler(

View File

@ -69,7 +69,11 @@ class PubSubItem {
} }
class PubSubManager extends XmppManagerBase { class PubSubManager extends XmppManagerBase {
PubSubManager() : super(pubsubManager); @override
String getId() => pubsubManager;
@override
String getName() => 'PubsubManager';
@override @override
List<StanzaHandler> getIncomingStanzaHandlers() => [ List<StanzaHandler> getIncomingStanzaHandlers() => [

View File

@ -8,6 +8,7 @@ import 'package:moxxmpp/src/stringxml.dart';
/// A data class representing the jabber:x:oob tag. /// A data class representing the jabber:x:oob tag.
class OOBData { class OOBData {
const OOBData({ this.url, this.desc }); const OOBData({ this.url, this.desc });
final String? url; final String? url;
final String? desc; final String? desc;
@ -31,7 +32,11 @@ XMLNode constructOOBNode(OOBData data) {
} }
class OOBManager extends XmppManagerBase { class OOBManager extends XmppManagerBase {
OOBManager() : super(oobManager); @override
String getName() => 'OOBName';
@override
String getId() => oobManager;
@override @override
List<String> getDiscoFeatures() => [ oobDataXmlns ]; List<String> getDiscoFeatures() => [ oobDataXmlns ];

View File

@ -28,24 +28,24 @@ class UserAvatarMetadata {
this.height, this.height,
this.mime, this.mime,
); );
/// The amount of bytes in the file /// The amount of bytes in the file
final int length; final int length;
/// The identifier of the avatar /// The identifier of the avatar
final String id; final String id;
/// Image proportions /// Image proportions
final int width; final int width;
final int height; final int height;
/// The MIME type of the avatar /// The MIME type of the avatar
final String mime; final String mime;
} }
/// NOTE: This class requires a PubSubManager /// NOTE: This class requires a PubSubManager
class UserAvatarManager extends XmppManagerBase { class UserAvatarManager extends XmppManagerBase {
UserAvatarManager() : super(userAvatarManager); @override
String getId() => userAvatarManager;
@override
String getName() => 'UserAvatarManager';
PubSubManager _getPubSubManager() => getAttributes().getManagerById(pubsubManager)! as PubSubManager; PubSubManager _getPubSubManager() => getAttributes().getManagerById(pubsubManager)! as PubSubManager;

View File

@ -39,11 +39,15 @@ ChatState chatStateFromString(String raw) {
String chatStateToString(ChatState state) => state.toString().split('.').last; String chatStateToString(ChatState state) => state.toString().split('.').last;
class ChatStateManager extends XmppManagerBase { class ChatStateManager extends XmppManagerBase {
ChatStateManager() : super(chatStateManager);
@override @override
List<String> getDiscoFeatures() => [ chatStateXmlns ]; List<String> getDiscoFeatures() => [ chatStateXmlns ];
@override
String getName() => 'ChatStateManager';
@override
String getId() => chatStateManager;
@override @override
List<StanzaHandler> getIncomingStanzaHandlers() => [ List<StanzaHandler> getIncomingStanzaHandlers() => [
StanzaHandler( StanzaHandler(

View File

@ -71,7 +71,7 @@ Future<String> calculateCapabilityHash(DiscoInfo info, HashAlgorithm algorithm)
/// the DiscoManager. /// the DiscoManager.
/// NOTE: This manager requires that the DiscoManager is also registered. /// NOTE: This manager requires that the DiscoManager is also registered.
class EntityCapabilitiesManager extends XmppManagerBase { class EntityCapabilitiesManager extends XmppManagerBase {
EntityCapabilitiesManager(this._capabilityHashBase) : super(entityCapabilitiesManager); EntityCapabilitiesManager(this._capabilityHashBase) : super();
/// The string that is both the node under which we advertise the disco info /// The string that is both the node under which we advertise the disco info
/// and the base for the actual node on which we respond to disco#info requests. /// and the base for the actual node on which we respond to disco#info requests.
@ -80,11 +80,19 @@ class EntityCapabilitiesManager extends XmppManagerBase {
/// The cached capability hash. /// The cached capability hash.
String? _capabilityHash; String? _capabilityHash;
@override
String getName() => 'EntityCapabilitiesManager';
@override
String getId() => entityCapabilitiesManager;
@override @override
Future<bool> isSupported() async => true; Future<bool> isSupported() async => true;
@override @override
List<String> getDiscoFeatures() => [ capsXmlns ]; List<String> getDiscoFeatures() => [
capsXmlns,
];
/// Computes, if required, the capability hash of the data provided by /// Computes, if required, the capability hash of the data provided by
/// the DiscoManager. /// the DiscoManager.

View File

@ -24,11 +24,15 @@ XMLNode makeMessageDeliveryResponse(String id) {
} }
class MessageDeliveryReceiptManager extends XmppManagerBase { class MessageDeliveryReceiptManager extends XmppManagerBase {
MessageDeliveryReceiptManager() : super(messageDeliveryReceiptManager);
@override @override
List<String> getDiscoFeatures() => [ deliveryXmlns ]; List<String> getDiscoFeatures() => [ deliveryXmlns ];
@override
String getName() => 'MessageDeliveryReceiptManager';
@override
String getId() => messageDeliveryReceiptManager;
@override @override
List<StanzaHandler> getIncomingStanzaHandlers() => [ List<StanzaHandler> getIncomingStanzaHandlers() => [
StanzaHandler( StanzaHandler(

View File

@ -9,10 +9,16 @@ import 'package:moxxmpp/src/stringxml.dart';
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart'; import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
class BlockingManager extends XmppManagerBase { class BlockingManager extends XmppManagerBase {
BlockingManager() : super(blockingManager); BlockingManager() : _supported = false, _gotSupported = false, super();
bool _supported = false; bool _supported;
bool _gotSupported = false; bool _gotSupported;
@override
String getId() => blockingManager;
@override
String getName() => 'BlockingManager';
@override @override
List<StanzaHandler> getIncomingStanzaHandlers() => [ List<StanzaHandler> getIncomingStanzaHandlers() => [

View File

@ -21,41 +21,40 @@ const xmlUintMax = 4294967296; // 2**32
typedef StanzaAckedCallback = bool Function(Stanza stanza); typedef StanzaAckedCallback = bool Function(Stanza stanza);
class StreamManagementManager extends XmppManagerBase { class StreamManagementManager extends XmppManagerBase {
StreamManagementManager({ StreamManagementManager({
this.ackTimeout = const Duration(seconds: 30), this.ackTimeout = const Duration(seconds: 30),
}) : super(smManager); })
: _state = StreamManagementState(0, 0),
_unackedStanzas = {},
_stateLock = Lock(),
_streamManagementEnabled = false,
_lastAckTimestamp = -1,
_pendingAcks = 0,
_streamResumed = false,
_ackLock = Lock();
/// The queue of stanzas that are not (yet) acked /// The queue of stanzas that are not (yet) acked
final Map<int, Stanza> _unackedStanzas = {}; final Map<int, Stanza> _unackedStanzas;
/// Commitable state of the StreamManagementManager /// Commitable state of the StreamManagementManager
StreamManagementState _state = StreamManagementState(0, 0); StreamManagementState _state;
/// Mutex lock for _state /// Mutex lock for _state
final Lock _stateLock = Lock(); final Lock _stateLock;
/// If the have enabled SM on the stream yet /// If the have enabled SM on the stream yet
bool _streamManagementEnabled = false; bool _streamManagementEnabled;
/// If the current stream has been resumed; /// If the current stream has been resumed;
bool _streamResumed = false; bool _streamResumed;
/// The time in which the response to an ack is still valid. Counts as a timeout /// The time in which the response to an ack is still valid. Counts as a timeout
/// otherwise /// otherwise
@internal @internal
final Duration ackTimeout; final Duration ackTimeout;
/// The time at which the last ack has been sent /// The time at which the last ack has been sent
int _lastAckTimestamp = -1; int _lastAckTimestamp;
/// The timer to see if we timed the connection out /// The timer to see if we timed the connection out
Timer? _ackTimer; Timer? _ackTimer;
/// Counts how many acks we're waiting for /// Counts how many acks we're waiting for
int _pendingAcks = 0; int _pendingAcks;
/// Lock for both [_lastAckTimestamp] and [_pendingAcks]. /// Lock for both [_lastAckTimestamp] and [_pendingAcks].
final Lock _ackLock = Lock(); final Lock _ackLock;
/// Functions for testing /// Functions for testing
@visibleForTesting @visibleForTesting
@ -121,6 +120,12 @@ class StreamManagementManager extends XmppManagerBase {
StreamManagementState get state => _state; StreamManagementState get state => _state;
bool get streamResumed => _streamResumed; bool get streamResumed => _streamResumed;
@override
String getId() => smManager;
@override
String getName() => 'StreamManagementManager';
@override @override
List<NonzaHandler> getNonzaHandlers() => [ List<NonzaHandler> getNonzaHandlers() => [

View File

@ -14,7 +14,11 @@ class DelayedDelivery {
} }
class DelayedDeliveryManager extends XmppManagerBase { class DelayedDeliveryManager extends XmppManagerBase {
DelayedDeliveryManager() : super(delayedDeliveryManager); @override
String getId() => delayedDeliveryManager;
@override
String getName() => 'DelayedDeliveryManager';
@override @override
Future<bool> isSupported() async => true; Future<bool> isSupported() async => true;

View File

@ -14,7 +14,7 @@ import 'package:moxxmpp/src/xeps/xep_0297.dart';
/// This manager class implements support for XEP-0280. /// This manager class implements support for XEP-0280.
class CarbonsManager extends XmppManagerBase { class CarbonsManager extends XmppManagerBase {
CarbonsManager() : super(carbonsManager); CarbonsManager() : super();
/// Indicates that message carbons are enabled. /// Indicates that message carbons are enabled.
bool _isEnabled = false; bool _isEnabled = false;
@ -25,6 +25,12 @@ class CarbonsManager extends XmppManagerBase {
/// Indicates that we know that [CarbonsManager._supported] is accurate. /// Indicates that we know that [CarbonsManager._supported] is accurate.
bool _gotSupported = false; bool _gotSupported = false;
@override
String getId() => carbonsManager;
@override
String getName() => 'CarbonsManager';
@override @override
List<StanzaHandler> getIncomingPreStanzaHandlers() => [ List<StanzaHandler> getIncomingPreStanzaHandlers() => [
StanzaHandler( StanzaHandler(

View File

@ -61,7 +61,11 @@ HashFunction hashFunctionFromName(String name) {
} }
class CryptographicHashManager extends XmppManagerBase { class CryptographicHashManager extends XmppManagerBase {
CryptographicHashManager() : super(cryptographicHashManager); @override
String getId() => cryptographicHashManager;
@override
String getName() => 'CryptographicHashManager';
@override @override
Future<bool> isSupported() async => true; Future<bool> isSupported() async => true;
@ -77,7 +81,7 @@ class CryptographicHashManager extends XmppManagerBase {
]; ];
static Future<List<int>> hashFromData(List<int> data, HashFunction function) async { static Future<List<int>> hashFromData(List<int> data, HashFunction function) async {
// TODO(PapaTutuWawa): Implement the others as well // TODO(PapaTutuWawa): Implemen the others as well
HashAlgorithm algo; HashAlgorithm algo;
switch (function) { switch (function) {
case HashFunction.sha256: case HashFunction.sha256:

View File

@ -17,7 +17,11 @@ XMLNode makeLastMessageCorrectionEdit(String id) {
} }
class LastMessageCorrectionManager extends XmppManagerBase { class LastMessageCorrectionManager extends XmppManagerBase {
LastMessageCorrectionManager() : super(lastMessageCorrectionManager); @override
String getName() => 'LastMessageCorrectionManager';
@override
String getId() => lastMessageCorrectionManager;
@override @override
List<String> getDiscoFeatures() => [ lmcXmlns ]; List<String> getDiscoFeatures() => [ lmcXmlns ];

View File

@ -25,7 +25,11 @@ XMLNode makeChatMarker(String tag, String id) {
} }
class ChatMarkerManager extends XmppManagerBase { class ChatMarkerManager extends XmppManagerBase {
ChatMarkerManager() : super(chatMarkerManager); @override
String getName() => 'ChatMarkerManager';
@override
String getId() => chatMarkerManager;
@override @override
List<String> getDiscoFeatures() => [ chatMarkersXmlns ]; List<String> getDiscoFeatures() => [ chatMarkersXmlns ];

View File

@ -26,10 +26,10 @@ class CSIInactiveNonza extends XMLNode {
/// A Stub negotiator that is just for "intercepting" the stream feature. /// A Stub negotiator that is just for "intercepting" the stream feature.
class CSINegotiator extends XmppFeatureNegotiatorBase { class CSINegotiator extends XmppFeatureNegotiatorBase {
CSINegotiator() : super(11, false, csiXmlns, csiNegotiator); CSINegotiator() : _supported = false, super(11, false, csiXmlns, csiNegotiator);
/// True if CSI is supported. False otherwise. /// True if CSI is supported. False otherwise.
bool _supported = false; bool _supported;
bool get isSupported => _supported; bool get isSupported => _supported;
@override @override
@ -50,9 +50,15 @@ class CSINegotiator extends XmppFeatureNegotiatorBase {
/// The manager requires a CSINegotiator to be registered as a feature negotiator. /// The manager requires a CSINegotiator to be registered as a feature negotiator.
class CSIManager extends XmppManagerBase { class CSIManager extends XmppManagerBase {
CSIManager() : super(csiManager);
bool _isActive = true; CSIManager() : _isActive = true, super();
bool _isActive;
@override
String getId() => csiManager;
@override
String getName() => 'CSIManager';
@override @override
Future<bool> isSupported() async { Future<bool> isSupported() async {

View File

@ -13,6 +13,7 @@ import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
/// NOTE: [StableStanzaId.stanzaId] must not be confused with the actual id attribute of /// NOTE: [StableStanzaId.stanzaId] must not be confused with the actual id attribute of
/// the message stanza. /// the message stanza.
class StableStanzaId { class StableStanzaId {
const StableStanzaId({ this.originId, this.stanzaId, this.stanzaIdBy }); const StableStanzaId({ this.originId, this.stanzaId, this.stanzaIdBy });
final String? originId; final String? originId;
final String? stanzaId; final String? stanzaId;
@ -28,7 +29,11 @@ XMLNode makeOriginIdElement(String id) {
} }
class StableIdManager extends XmppManagerBase { class StableIdManager extends XmppManagerBase {
StableIdManager() : super(stableIdManager); @override
String getName() => 'StableIdManager';
@override
String getId() => stableIdManager;
@override @override
List<String> getDiscoFeatures() => [ stableIdXmlns ]; List<String> getDiscoFeatures() => [ stableIdXmlns ];

View File

@ -41,19 +41,17 @@ Map<String, String> prepareHeaders(Map<String, String> headers) {
} }
class HttpFileUploadManager extends XmppManagerBase { class HttpFileUploadManager extends XmppManagerBase {
HttpFileUploadManager() : super(httpFileUploadManager); HttpFileUploadManager() : _gotSupported = false, _supported = false, super();
/// The entity that we will request file uploads from, if discovered.
JID? _entityJid; JID? _entityJid;
/// The maximum file upload file size, if advertised and discovered.
int? _maxUploadSize; int? _maxUploadSize;
bool _gotSupported;
bool _supported;
/// Flag, if we every tried to discover the upload entity. @override
bool _gotSupported = false; String getId() => httpFileUploadManager;
/// Flag, if we can use HTTP File Upload @override
bool _supported = false; String getName() => 'HttpFileUploadManager';
/// Returns whether the entity provided an identity that tells us that we can ask it /// Returns whether the entity provided an identity that tells us that we can ask it
/// for an HTTP upload slot. /// for an HTTP upload slot.

View File

@ -53,12 +53,20 @@ XMLNode buildEmeElement(ExplicitEncryptionType type) {
} }
class EmeManager extends XmppManagerBase { class EmeManager extends XmppManagerBase {
EmeManager() : super(emeManager);
EmeManager() : super();
@override
String getId() => emeManager;
@override
String getName() => 'EmeManager';
@override @override
Future<bool> isSupported() async => true; Future<bool> isSupported() async => true;
@override @override
List<String> getDiscoFeatures() => [ emeXmlns ]; List<String> getDiscoFeatures() => [emeXmlns];
@override @override
List<StanzaHandler> getIncomingStanzaHandlers() => [ List<StanzaHandler> getIncomingStanzaHandlers() => [

View File

@ -42,9 +42,12 @@ const _doNotEncryptList = [
DoNotEncrypt('stanza-id', stableIdXmlns), DoNotEncrypt('stanza-id', stableIdXmlns),
]; ];
@mustCallSuper
abstract class BaseOmemoManager extends XmppManagerBase { abstract class BaseOmemoManager extends XmppManagerBase {
BaseOmemoManager() : super(omemoManager); @override
String getId() => omemoManager;
@override
String getName() => 'OmemoManager';
// TODO(Unknown): Technically, this is not always true // TODO(Unknown): Technically, this is not always true
@override @override

View File

@ -8,6 +8,7 @@ import 'package:moxxmpp/src/stringxml.dart';
import 'package:moxxmpp/src/xeps/staging/extensible_file_thumbnails.dart'; import 'package:moxxmpp/src/xeps/staging/extensible_file_thumbnails.dart';
class StatelessMediaSharingData { class StatelessMediaSharingData {
const StatelessMediaSharingData({ required this.mediaType, required this.size, required this.description, required this.hashes, required this.url, required this.thumbnails }); const StatelessMediaSharingData({ required this.mediaType, required this.size, required this.description, required this.hashes, required this.url, required this.thumbnails });
final String mediaType; final String mediaType;
final int size; final int size;
@ -62,7 +63,11 @@ StatelessMediaSharingData parseSIMSElement(XMLNode node) {
} }
class SIMSManager extends XmppManagerBase { class SIMSManager extends XmppManagerBase {
SIMSManager() : super(simsManager); @override
String getName() => 'SIMSManager';
@override
String getId() => simsManager;
@override @override
List<String> getDiscoFeatures() => [ simsXmlns ]; List<String> getDiscoFeatures() => [ simsXmlns ];

View File

@ -12,7 +12,11 @@ class MessageRetractionData {
} }
class MessageRetractionManager extends XmppManagerBase { class MessageRetractionManager extends XmppManagerBase {
MessageRetractionManager() : super(messageRetractionManager); @override
String getName() => 'MessageRetractionManager';
@override
String getId() => messageRetractionManager;
@override @override
List<String> getDiscoFeatures() => [ messageRetractionXmlns ]; List<String> getDiscoFeatures() => [ messageRetractionXmlns ];

View File

@ -29,11 +29,15 @@ class MessageReactions {
} }
class MessageReactionsManager extends XmppManagerBase { class MessageReactionsManager extends XmppManagerBase {
MessageReactionsManager() : super(messageReactionsManager);
@override @override
List<String> getDiscoFeatures() => [ messageReactionsXmlns ]; List<String> getDiscoFeatures() => [ messageReactionsXmlns ];
@override
String getName() => 'MessageReactionsManager';
@override
String getId() => messageReactionsManager;
@override @override
List<StanzaHandler> getIncomingStanzaHandlers() => [ List<StanzaHandler> getIncomingStanzaHandlers() => [
StanzaHandler( StanzaHandler(

View File

@ -105,7 +105,11 @@ class StatelessFileSharingData {
} }
class SFSManager extends XmppManagerBase { class SFSManager extends XmppManagerBase {
SFSManager() : super(sfsManager); @override
String getName() => 'SFSManager';
@override
String getId() => sfsManager;
@override @override
List<StanzaHandler> getIncomingStanzaHandlers() => [ List<StanzaHandler> getIncomingStanzaHandlers() => [

View File

@ -225,7 +225,11 @@ class StickerPack {
} }
class StickersManager extends XmppManagerBase { class StickersManager extends XmppManagerBase {
StickersManager() : super(stickersManager); @override
String getId() => stickersManager;
@override
String getName() => 'StickersManager';
@override @override
Future<bool> isSupported() async => true; Future<bool> isSupported() async => true;

View File

@ -66,7 +66,11 @@ class QuoteData {
/// A manager implementing support for parsing XEP-0461 metadata. The /// A manager implementing support for parsing XEP-0461 metadata. The
/// MessageRepliesManager itself does not modify the body of the message. /// MessageRepliesManager itself does not modify the body of the message.
class MessageRepliesManager extends XmppManagerBase { class MessageRepliesManager extends XmppManagerBase {
MessageRepliesManager() : super(messageRepliesManager); @override
String getName() => 'MessageRepliesManager';
@override
String getId() => messageRepliesManager;
@override @override
List<String> getDiscoFeatures() => [ List<String> getDiscoFeatures() => [

View File

@ -1,40 +0,0 @@
import 'package:test/test.dart';
import 'package:moxxmpp/src/util/wait.dart';
void main() {
test('Test adding and resolving', () async {
// ID -> Milliseconds since epoch
final tracker = WaitForTracker<int, int>();
int r2 = 0;
int r3 = 0;
// Queue some jobs
final r1 = await tracker.waitFor(0);
expect(r1, null);
tracker
.waitFor(0)
.then((result) async {
expect(result != null, true);
r2 = await result!;
});
tracker
.waitFor(0)
.then((result) async {
expect(result != null, true);
r3 = await result!;
});
final c = await tracker.waitFor(1);
expect(c, null);
// Resolve jobs
await tracker.resolve(0, 42);
await tracker.resolve(1, 25);
await tracker.resolve(2, -1);
expect(r2, 42);
expect(r3, 42);
});
}

View File

@ -1,13 +1,9 @@
import 'package:moxxmpp/moxxmpp.dart'; import 'package:moxxmpp/moxxmpp.dart';
import 'package:moxxmpp/src/xeps/xep_0030/cache.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import '../helpers/logging.dart';
import '../helpers/xmpp.dart'; import '../helpers/xmpp.dart';
void main() { void main() {
initLogger();
test('Test having multiple disco requests for the same JID', () async { test('Test having multiple disco requests for the same JID', () async {
final fakeSocket = StubTCPSocket( final fakeSocket = StubTCPSocket(
play: [ play: [
@ -106,7 +102,7 @@ void main() {
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1));
expect( expect(
disco.infoTracker.getRunningTasks(DiscoCacheKey(jid.toString(), null)).length, disco.getRunningInfoQueries(DiscoCacheKey(jid.toString(), null)).length,
1, 1,
); );
fakeSocket.injectRawXml("<iq type='result' id='${fakeSocket.lastId!}' from='romeo@montague.lit/orchard' to='polynomdivision@test.server/MU29eEZn' xmlns='jabber:client'><query xmlns='http://jabber.org/protocol/disco#info' /></iq>"); fakeSocket.injectRawXml("<iq type='result' id='${fakeSocket.lastId!}' from='romeo@montague.lit/orchard' to='polynomdivision@test.server/MU29eEZn' xmlns='jabber:client'><query xmlns='http://jabber.org/protocol/disco#info' /></iq>");
@ -115,6 +111,6 @@ void main() {
expect(fakeSocket.getState(), 6); expect(fakeSocket.getState(), 6);
expect(await result1, await result2); expect(await result1, await result2);
expect(disco.infoTracker.hasTasksRunning(), false); expect(disco.hasInfoQueriesRunning(), false);
}); });
} }