immich/mobile/lib/infrastructure/loaders/remote_image_request.dart

122 lines
3 KiB
Dart
Raw Normal View History

part of 'image_request.dart';
class RemoteImageRequest extends ImageRequest {
2025-08-29 18:26:42 -04:00
static final _client = const NetworkRepository().getHttpClient(
directoryName: 'thumbnails',
diskCapacity: kThumbnailDiskCacheSize,
memoryCapacity: 0,
maxConnections: 16,
cacheMode: CacheMode.disk,
);
final String uri;
final Map<String, String> headers;
2025-08-29 18:26:42 -04:00
final abortTrigger = Completer<void>();
2025-08-29 18:26:42 -04:00
RemoteImageRequest({required this.uri, required this.headers});
@override
Future<ImageInfo?> load(ImageDecoderCallback decode, {double scale = 1.0}) async {
if (_isCancelled) {
return null;
}
try {
2025-08-29 18:26:42 -04:00
final buffer = await _downloadImage();
if (buffer == null) {
return null;
}
return await _decodeBuffer(buffer, decode, scale);
} catch (e) {
if (_isCancelled) {
return null;
}
rethrow;
}
}
2025-08-29 18:26:42 -04:00
Future<ImmutableBuffer?> _downloadImage() async {
if (_isCancelled) {
return null;
}
2025-08-29 18:26:42 -04:00
final req = http.AbortableRequest('get', Uri.parse(uri), abortTrigger: abortTrigger.future);
req.headers.addAll(headers);
final res = await _client.send(req);
if (_isCancelled) {
2025-08-29 18:26:42 -04:00
_onCancelled();
return null;
}
2025-08-29 18:26:42 -04:00
final stream = res.stream.map((chunk) {
if (_isCancelled) {
throw StateError('Cancelled request');
}
return chunk;
});
try {
2025-08-29 18:26:42 -04:00
final Uint8List bytes = await _downloadBytes(stream, res.contentLength ?? -1);
if (_isCancelled) {
return null;
}
return await ImmutableBuffer.fromUint8List(bytes);
} catch (e) {
if (_isCancelled) {
return null;
}
rethrow;
}
}
Future<Uint8List> _downloadBytes(Stream<List<int>> stream, int length) async {
final Uint8List bytes;
int offset = 0;
if (length > 0) {
// Known content length - use pre-allocated buffer
bytes = Uint8List(length);
await stream.listen((chunk) {
bytes.setAll(offset, chunk);
offset += chunk.length;
}, cancelOnError: true).asFuture();
} else {
// Unknown content length - collect chunks dynamically
final chunks = <List<int>>[];
int totalLength = 0;
await stream.listen((chunk) {
chunks.add(chunk);
totalLength += chunk.length;
}, cancelOnError: true).asFuture();
bytes = Uint8List(totalLength);
for (final chunk in chunks) {
bytes.setAll(offset, chunk);
offset += chunk.length;
}
}
return bytes;
}
Future<ImageInfo?> _decodeBuffer(ImmutableBuffer buffer, ImageDecoderCallback decode, scale) async {
if (_isCancelled) {
buffer.dispose();
return null;
}
final codec = await decode(buffer);
if (_isCancelled) {
buffer.dispose();
codec.dispose();
return null;
}
final frame = await codec.getNextFrame();
return ImageInfo(image: frame.image, scale: scale);
}
@override
void _onCancelled() {
2025-08-29 18:26:42 -04:00
abortTrigger.complete();
}
}