feat: Document the RosterStateManager better
This commit is contained in:
parent
995f2e0248
commit
2581bbe203
@ -1,6 +1,7 @@
|
|||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/roster/roster.dart';
|
import 'package:moxxmpp/src/roster/roster.dart';
|
||||||
|
import 'package:synchronized/synchronized.dart';
|
||||||
|
|
||||||
class _RosterProcessTriple {
|
class _RosterProcessTriple {
|
||||||
const _RosterProcessTriple(this.removed, this.modified, this.added);
|
const _RosterProcessTriple(this.removed, this.modified, this.added);
|
||||||
@ -15,33 +16,65 @@ class RosterCacheLoadResult {
|
|||||||
final List<XmppRosterItem> roster;
|
final List<XmppRosterItem> roster;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This class manages the roster state in order to correctly process and persist
|
||||||
|
/// roster pushes and facilitate roster versioning requests.
|
||||||
abstract class BaseRosterStateManager {
|
abstract class BaseRosterStateManager {
|
||||||
List<XmppRosterItem>? currentRoster;
|
/// The cached version of the roster. If null, then it has not been loaded yet.
|
||||||
String? currentVersion;
|
List<XmppRosterItem>? _currentRoster;
|
||||||
|
|
||||||
|
/// The cached version of the roster version.
|
||||||
|
String? _currentVersion;
|
||||||
|
|
||||||
|
/// A critical section locking both _currentRoster and _currentVersion.
|
||||||
|
final Lock _lock = Lock();
|
||||||
|
|
||||||
|
/// Overrideable function
|
||||||
|
/// Loads the old cached version of the roster and optionally that roster version
|
||||||
|
/// from persistent storage into a RosterCacheLoadResult object.
|
||||||
Future<RosterCacheLoadResult> loadRosterCache();
|
Future<RosterCacheLoadResult> loadRosterCache();
|
||||||
|
|
||||||
|
/// Overrideable function
|
||||||
|
/// Commits the roster data to persistent storage.
|
||||||
|
///
|
||||||
|
/// [version] is the roster version string. If none was provided, then this value
|
||||||
|
/// is null.
|
||||||
|
///
|
||||||
|
/// [removed] is a (possibly empty) list of bare JIDs that are removed from the
|
||||||
|
/// roster.
|
||||||
|
///
|
||||||
|
/// [modified] is a (possibly empty) list of XmppRosterItems that are modified. Correlation with
|
||||||
|
/// the cache is done using its jid attribute.
|
||||||
|
///
|
||||||
|
/// [added] is a (possibly empty) list of XmppRosterItems that are added by the
|
||||||
|
/// roster push or roster fetch request.
|
||||||
Future<void> commitRoster(String? version, List<String> removed, List<XmppRosterItem> modified, List<XmppRosterItem> added);
|
Future<void> commitRoster(String? version, List<String> removed, List<XmppRosterItem> modified, List<XmppRosterItem> added);
|
||||||
|
|
||||||
|
/// Load and cache or return the cached roster version.
|
||||||
Future<String?> getRosterVersion() async {
|
Future<String?> getRosterVersion() async {
|
||||||
await _loadRosterCache();
|
return _lock.synchronized(() async {
|
||||||
|
await _loadRosterCache();
|
||||||
|
|
||||||
return currentVersion;
|
return _currentVersion;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Loads the cached roster data into memory, if that has not already happened.
|
||||||
|
/// NOTE: Must be called from within the _lock critical section.
|
||||||
Future<void> _loadRosterCache() async {
|
Future<void> _loadRosterCache() async {
|
||||||
if (currentRoster == null) {
|
if (_currentRoster == null) {
|
||||||
final result = await loadRosterCache();
|
final result = await loadRosterCache();
|
||||||
|
|
||||||
currentRoster = result.roster;
|
_currentRoster = result.roster;
|
||||||
currentVersion = result.version;
|
_currentVersion = result.version;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Processes only single XmppRosterItem [item].
|
||||||
|
/// NOTE: Requires to be called from within the _lock critical section.
|
||||||
_RosterProcessTriple _handleRosterItem(XmppRosterItem item) {
|
_RosterProcessTriple _handleRosterItem(XmppRosterItem item) {
|
||||||
if (item.subscription == 'remove') {
|
if (item.subscription == 'remove') {
|
||||||
// The item has been removed
|
// The item has been removed
|
||||||
currentRoster!.removeWhere((i) => i.jid == item.jid);
|
_currentRoster!.removeWhere((i) => i.jid == item.jid);
|
||||||
return _RosterProcessTriple(
|
return _RosterProcessTriple(
|
||||||
item.jid,
|
item.jid,
|
||||||
null,
|
null,
|
||||||
@ -49,10 +82,10 @@ abstract class BaseRosterStateManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final index = currentRoster!.indexWhere((i) => i.jid == item.jid);
|
final index = _currentRoster!.indexWhere((i) => i.jid == item.jid);
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
// The item does not exist
|
// The item does not exist
|
||||||
currentRoster!.add(item);
|
_currentRoster!.add(item);
|
||||||
return _RosterProcessTriple(
|
return _RosterProcessTriple(
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
@ -60,7 +93,7 @@ abstract class BaseRosterStateManager {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// The item is updated
|
// The item is updated
|
||||||
currentRoster![index] = item;
|
_currentRoster![index] = item;
|
||||||
return _RosterProcessTriple(
|
return _RosterProcessTriple(
|
||||||
null,
|
null,
|
||||||
item,
|
item,
|
||||||
@ -69,59 +102,68 @@ abstract class BaseRosterStateManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handles a roster push from the RosterManager.
|
||||||
Future<void> handleRosterPush(RosterPushEvent event) async {
|
Future<void> handleRosterPush(RosterPushEvent event) async {
|
||||||
await _loadRosterCache();
|
await _lock.synchronized(() async {
|
||||||
|
await _loadRosterCache();
|
||||||
|
|
||||||
currentVersion = event.ver;
|
_currentVersion = event.ver;
|
||||||
final result = _handleRosterItem(event.item);
|
final result = _handleRosterItem(event.item);
|
||||||
|
|
||||||
if (result.removed != null) {
|
if (result.removed != null) {
|
||||||
return commitRoster(
|
return commitRoster(
|
||||||
currentVersion,
|
_currentVersion,
|
||||||
[result.removed!],
|
[result.removed!],
|
||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
} else if (result.modified != null) {
|
} else if (result.modified != null) {
|
||||||
return commitRoster(
|
return commitRoster(
|
||||||
currentVersion,
|
_currentVersion,
|
||||||
[],
|
[],
|
||||||
[result.modified!],
|
[result.modified!],
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
} else if (result.added != null) {
|
} else if (result.added != null) {
|
||||||
return commitRoster(
|
return commitRoster(
|
||||||
currentVersion,
|
_currentVersion,
|
||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
[result.added!],
|
[result.added!],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handles the result from a roster fetch.
|
||||||
Future<void> handleRosterFetch(RosterRequestResult result) async {
|
Future<void> handleRosterFetch(RosterRequestResult result) async {
|
||||||
final removed = List<String>.empty(growable: true);
|
await _lock.synchronized(() async {
|
||||||
final modified = List<XmppRosterItem>.empty(growable: true);
|
final removed = List<String>.empty(growable: true);
|
||||||
final added = List<XmppRosterItem>.empty(growable: true);
|
final modified = List<XmppRosterItem>.empty(growable: true);
|
||||||
|
final added = List<XmppRosterItem>.empty(growable: true);
|
||||||
|
|
||||||
await _loadRosterCache();
|
await _loadRosterCache();
|
||||||
|
|
||||||
currentVersion = result.ver;
|
_currentVersion = result.ver;
|
||||||
for (final item in result.items) {
|
for (final item in result.items) {
|
||||||
final result = _handleRosterItem(item);
|
final result = _handleRosterItem(item);
|
||||||
|
|
||||||
if (result.removed != null) removed.add(result.removed!);
|
if (result.removed != null) removed.add(result.removed!);
|
||||||
if (result.modified != null) modified.add(result.modified!);
|
if (result.modified != null) modified.add(result.modified!);
|
||||||
if (result.added != null) added.add(result.added!);
|
if (result.added != null) added.add(result.added!);
|
||||||
}
|
}
|
||||||
|
|
||||||
await commitRoster(
|
await commitRoster(
|
||||||
currentVersion,
|
_currentVersion,
|
||||||
removed,
|
removed,
|
||||||
modified,
|
modified,
|
||||||
added,
|
added,
|
||||||
);
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
List<XmppRosterItem> getRosterItems() => _currentRoster!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
|
@ -15,11 +15,11 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
rs.currentRoster!.indexWhere((item) => item.jid == 'testuser@server.example') != -1,
|
rs.getRosterItems().indexWhere((item) => item.jid == 'testuser@server.example') != -1,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
expect(rs.loadCount, 1);
|
expect(rs.loadCount, 1);
|
||||||
expect(rs.currentRoster!.length, 1);
|
expect(rs.getRosterItems().length, 1);
|
||||||
|
|
||||||
// Receive another roster push
|
// Receive another roster push
|
||||||
await rs.handleRosterPush(
|
await rs.handleRosterPush(
|
||||||
@ -32,11 +32,11 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
rs.currentRoster!.indexWhere((item) => item.jid == 'testuser2@server2.example') != -1,
|
rs.getRosterItems().indexWhere((item) => item.jid == 'testuser2@server2.example') != -1,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
expect(rs.loadCount, 1);
|
expect(rs.loadCount, 1);
|
||||||
expect(rs.currentRoster!.length, 2);
|
expect(rs.getRosterItems().length, 2);
|
||||||
|
|
||||||
// Remove one of the items
|
// Remove one of the items
|
||||||
await rs.handleRosterPush(
|
await rs.handleRosterPush(
|
||||||
@ -49,15 +49,15 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
rs.currentRoster!.indexWhere((item) => item.jid == 'testuser2@server2.example') == -1,
|
rs.getRosterItems().indexWhere((item) => item.jid == 'testuser2@server2.example') == -1,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
rs.currentRoster!.indexWhere((item) => item.jid == 'testuser@server.example') != 1,
|
rs.getRosterItems().indexWhere((item) => item.jid == 'testuser@server.example') != 1,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
expect(rs.loadCount, 1);
|
expect(rs.loadCount, 1);
|
||||||
expect(rs.currentRoster!.length, 1);
|
expect(rs.getRosterItems().length, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Test a roster fetch', () async {
|
test('Test a roster fetch', () async {
|
||||||
@ -85,9 +85,9 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(rs.loadCount, 1);
|
expect(rs.loadCount, 1);
|
||||||
expect(rs.currentRoster!.length, 3);
|
expect(rs.getRosterItems().length, 3);
|
||||||
expect(rs.currentRoster!.indexWhere((item) => item.jid == 'testuser@server.example') != -1, true);
|
expect(rs.getRosterItems().indexWhere((item) => item.jid == 'testuser@server.example') != -1, true);
|
||||||
expect(rs.currentRoster!.indexWhere((item) => item.jid == 'testuser2@server2.example') != -1, true);
|
expect(rs.getRosterItems().indexWhere((item) => item.jid == 'testuser2@server2.example') != -1, true);
|
||||||
expect(rs.currentRoster!.indexWhere((item) => item.jid == 'testuser3@server3.example') != -1, true);
|
expect(rs.getRosterItems().indexWhere((item) => item.jid == 'testuser3@server3.example') != -1, true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user