Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 32 additions & 2 deletions packages/neon_framework/lib/src/utils/request_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:neon_framework/src/bloc/result.dart';
import 'package:neon_framework/storage.dart';
import 'package:neon_http_client/neon_http_client.dart';
import 'package:nextcloud/utils.dart';
import 'package:queue/queue.dart';
import 'package:rxdart/rxdart.dart';
import 'package:timezone/timezone.dart' as tz;

Expand Down Expand Up @@ -67,7 +68,12 @@ class RequestManager {
@visibleForTesting
http.Client? httpClient;

/// Executes a generic [http.Request].
/// This allows us to limit the amount of parallel requests to the server.
/// This is especially important when opening a gallery with a lot of images which require previews.
final _requestQueue = Queue(parallel: 10);

/// Executes a generic [http.Request] in a queued manner.
/// Only actual requests that are not cached will be executed against the server and queued.
Future<void> wrap<T, R>({
required Account account,
required BehaviorSubject<Result<T>> subject,
Expand All @@ -79,11 +85,12 @@ class RequestManager {
if (subject.isClosed) {
return;
}

if (!subject.hasValue) {
subject.add(Result.loading());
}

var request = getRequest();
final request = getRequest();

final cachedResponse = await _cache?.get(account, request);
if (subject.isClosed) {
Expand Down Expand Up @@ -160,6 +167,29 @@ class RequestManager {
subject.add(subject.value.asLoading());
}

await _requestQueue.add(
() => _requestFromServer(
request: request,
account: account,
subject: subject,
getRequest: getRequest,
converter: converter,
unwrap: unwrap,
getCacheHeaders: getCacheHeaders,
),
);
}

/// Performs [http.Request] against the server.
Future<void> _requestFromServer<T, R>({
required http.Request request,
required Account account,
required BehaviorSubject<Result<T>> subject,
required http.Request Function() getRequest,
required Converter<http.Response, R> converter,
required UnwrapCallback<T, R> unwrap,
AsyncValueGetter<Map<String, String>>? getCacheHeaders,
}) async {
final client = httpClient ?? account.client;

for (var i = 0; i < kMaxTries; i++) {
Expand Down
55 changes: 42 additions & 13 deletions packages/neon_framework/packages/talk_app/test/room_bloc_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -320,37 +320,66 @@ void main() {

test('refresh', () async {
expect(
roomBloc.room.transformResult((e) => e.token),
roomBloc.lastCommonRead,
emitsInOrder([0, 0]),
);

final initialRoom = expectLater(
roomBloc.room.transformResult((room) => room.token),
emitsInOrder([
Result.success('abcd').asLoading(),
Result.success('abcd'),
Result.success('abcd'),
]),
);

final initialMessages = expectLater(
roomBloc.messages.transformResult(
(messages) => BuiltList<int>(messages.map((message) => message.id)),
),
emitsInOrder([
Result<BuiltList<int>>.loading(),
Result.success(BuiltList<int>([2, 1, 0])),
]),
);

await Future.wait([
initialRoom,
initialMessages,
]);

final refreshedRoom = expectLater(
roomBloc.room
.skip(1) // we are skipping the initial refresh
.transformResult((room) => room.token),
emitsInOrder([
Result.success('abcd').asLoading(),
Result.success('abcd'),
Result.success('abcd'),
]),
);

expect(
roomBloc.messages.transformResult((e) => BuiltList<int>(e.map((m) => m.id))),
final refreshedMessages = expectLater(
roomBloc.messages
.skip(1) // we are skipping the initial refresh
.transformResult(
(messages) => BuiltList<int>(messages.map((message) => message.id)),
),
emitsInOrder([
Result<BuiltList<int>>.loading(),
Result.success(BuiltList<int>([2, 1, 0])),
Result.success(BuiltList<int>([2, 1, 0])).asLoading(),
Result.success(BuiltList<int>([2, 1, 0])),
]),
);

expect(
roomBloc.lastCommonRead,
emitsInOrder([0, 0]),
);

// The delay is necessary to avoid a race condition with loading twice at the same time
await Future<void>.delayed(const Duration(milliseconds: 1));
await roomBloc.refresh();

verify(() => talkBloc.updateRoom(any())).called(4);
await Future.wait([
refreshedRoom,
refreshedMessages,
]);

// expecting 5 events: initial seed + 2 from constructor refresh + 2 from test refresh
verify(() => talkBloc.updateRoom(any())).called(5);
});

test('sendMessage', () async {
Expand Down
1 change: 1 addition & 0 deletions packages/neon_framework/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ dependencies:
path_provider: ^2.1.0
permission_handler: ^12.0.0
provider: ^6.0.0
queue: ^3.0.0
quick_actions: ^1.0.0
rxdart: ^0.28.0
scrollable_positioned_list: ^0.3.0
Expand Down
2 changes: 1 addition & 1 deletion packages/neon_lints/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ environment:

dependencies:
analyzer: ^7.3.0
analyzer_plugin: ^0.13.0
analyzer_plugin: ^0.13.1
custom_lint_builder: ^0.7.0

dev_dependencies:
Expand Down
Loading