@@ -79,68 +79,87 @@ List<Object?> mapParameters(List<Object?> parameters) {
79
79
}
80
80
81
81
extension ThrottledUpdates on CommonDatabase {
82
- /// Wraps [updatesSync] to:
82
+ /// An unthrottled stream of updated tables that emits on every commit.
83
83
///
84
- /// - Not fire in transactions.
85
- /// - Fire asynchronously.
86
- /// - Only report table names, which are buffered to avoid duplicates.
87
- Stream <Set <String >> get throttledUpdatedTables {
88
- StreamController <Set <String >>? controller;
89
- var pendingUpdates = < String > {};
90
- var paused = false ;
91
-
92
- Timer ? updateDebouncer;
93
-
94
- void maybeFireUpdates () {
95
- updateDebouncer? .cancel ();
96
- updateDebouncer = null ;
97
-
98
- if (paused) {
99
- // Continue collecting updates, but don't fire any
100
- return ;
84
+ /// A paused subscription on this stream will buffer changed tables into a
85
+ /// growing set instead of losing events, so this stream is simple to throttle
86
+ /// downstream.
87
+ Stream <Set <String >> get updatedTables {
88
+ final listeners = < _UpdateListener > [];
89
+ var uncommitedUpdates = < String > {};
90
+ var underlyingSubscriptions = < StreamSubscription <void >> [];
91
+
92
+ void handleUpdate (SqliteUpdate update) {
93
+ uncommitedUpdates.add (update.tableName);
94
+ }
95
+
96
+ void afterCommit () {
97
+ for (final listener in listeners) {
98
+ listener.notify (uncommitedUpdates);
101
99
}
102
100
103
- if (! autocommit) {
104
- // Inside a transaction - do not fire updates
105
- return ;
101
+ uncommitedUpdates.clear ();
102
+ }
103
+
104
+ void afterRollback () {
105
+ uncommitedUpdates.clear ();
106
+ }
107
+
108
+ void addListener (_UpdateListener listener) {
109
+ listeners.add (listener);
110
+
111
+ if (listeners.length == 1 ) {
112
+ // First listener, start listening for raw updates on underlying
113
+ // database.
114
+ underlyingSubscriptions = [
115
+ updatesSync.listen (handleUpdate),
116
+ commits.listen ((_) => afterCommit ()),
117
+ commits.listen ((_) => afterRollback ())
118
+ ];
106
119
}
120
+ }
107
121
108
- if (pendingUpdates.isNotEmpty) {
109
- controller! .add (pendingUpdates);
110
- pendingUpdates = {};
122
+ void removeListener (_UpdateListener listener) {
123
+ listeners.remove (listener);
124
+ if (listeners.isEmpty) {
125
+ for (final sub in underlyingSubscriptions) {
126
+ sub.cancel ();
127
+ }
111
128
}
112
129
}
113
130
114
- void collectUpdate (SqliteUpdate event) {
115
- pendingUpdates.add (event.tableName);
131
+ return Stream .multi (
132
+ (listener) {
133
+ final wrapped = _UpdateListener (listener);
134
+ addListener (wrapped);
116
135
117
- updateDebouncer ?? =
118
- Timer (const Duration (milliseconds: 1 ), maybeFireUpdates);
136
+ listener.onCancel = () => removeListener (wrapped);
137
+ },
138
+ isBroadcast: true ,
139
+ );
140
+ }
141
+ }
142
+
143
+ class _UpdateListener {
144
+ final MultiStreamController <Set <String >> downstream;
145
+ Set <String > buffered = {};
146
+
147
+ _UpdateListener (this .downstream);
148
+
149
+ void notify (Set <String > pendingUpdates) {
150
+ buffered.addAll (pendingUpdates);
151
+ if (! downstream.isPaused) {
152
+ downstream.add (buffered);
153
+ buffered = {};
119
154
}
155
+ }
156
+ }
120
157
121
- StreamSubscription ? txSubscription;
122
- StreamSubscription ? sourceSubscription;
123
-
124
- controller = StreamController (onListen: () {
125
- txSubscription = commits.listen ((_) {
126
- maybeFireUpdates ();
127
- }, onError: (error) {
128
- controller? .addError (error);
129
- });
130
-
131
- sourceSubscription = updatesSync.listen (collectUpdate, onError: (error) {
132
- controller? .addError (error);
133
- });
134
- }, onPause: () {
135
- paused = true ;
136
- }, onResume: () {
137
- paused = false ;
138
- maybeFireUpdates ();
139
- }, onCancel: () {
140
- txSubscription? .cancel ();
141
- sourceSubscription? .cancel ();
142
- });
143
-
144
- return controller.stream;
158
+ extension StreamUtils <T > on Stream <T > {
159
+ Stream <T > pauseAfterEvent (Duration duration) async * {
160
+ await for (final event in this ) {
161
+ yield event;
162
+ await Future .delayed (duration);
163
+ }
145
164
}
146
165
}
0 commit comments