mirror of
https://github.com/immich-app/immich
synced 2025-10-17 18:19:27 +00:00
fix(mobile): caching thumbnails to disk (#21275)
This commit is contained in:
parent
19c53609e1
commit
e67265cef2
3 changed files with 51 additions and 26 deletions
|
|
@ -65,40 +65,53 @@ class RemoteImageRequest extends ImageRequest {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle unknown content length from reverse proxy
|
final cacheManager = this.cacheManager;
|
||||||
final contentLength = response.contentLength;
|
final streamController = StreamController<List<int>>(sync: true);
|
||||||
final Uint8List bytes;
|
final Stream<List<int>> stream;
|
||||||
int offset = 0;
|
cacheManager?.putStreamedFile(url, streamController.stream);
|
||||||
|
stream = response.map((chunk) {
|
||||||
if (contentLength >= 0) {
|
|
||||||
// Known content length - use pre-allocated buffer
|
|
||||||
bytes = Uint8List(contentLength);
|
|
||||||
final subscription = response.listen((List<int> chunk) {
|
|
||||||
// this is important to break the response stream if the request is cancelled
|
|
||||||
if (_isCancelled) {
|
if (_isCancelled) {
|
||||||
throw StateError('Cancelled request');
|
throw StateError('Cancelled request');
|
||||||
}
|
}
|
||||||
|
if (cacheManager != null) {
|
||||||
|
streamController.add(chunk);
|
||||||
|
}
|
||||||
|
return chunk;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
final Uint8List bytes = await _downloadBytes(stream, response.contentLength);
|
||||||
|
streamController.close();
|
||||||
|
return await ImmutableBuffer.fromUint8List(bytes);
|
||||||
|
} catch (e) {
|
||||||
|
streamController.addError(e);
|
||||||
|
streamController.close();
|
||||||
|
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);
|
bytes.setAll(offset, chunk);
|
||||||
offset += chunk.length;
|
offset += chunk.length;
|
||||||
}, cancelOnError: true);
|
}, cancelOnError: true).asFuture();
|
||||||
cacheManager?.putStreamedFile(url, response);
|
|
||||||
await subscription.asFuture();
|
|
||||||
} else {
|
} else {
|
||||||
// Unknown content length - collect chunks dynamically
|
// Unknown content length - collect chunks dynamically
|
||||||
final chunks = <List<int>>[];
|
final chunks = <List<int>>[];
|
||||||
int totalLength = 0;
|
int totalLength = 0;
|
||||||
final subscription = response.listen((List<int> chunk) {
|
await stream.listen((chunk) {
|
||||||
// this is important to break the response stream if the request is cancelled
|
|
||||||
if (_isCancelled) {
|
|
||||||
throw StateError('Cancelled request');
|
|
||||||
}
|
|
||||||
chunks.add(chunk);
|
chunks.add(chunk);
|
||||||
totalLength += chunk.length;
|
totalLength += chunk.length;
|
||||||
}, cancelOnError: true);
|
}, cancelOnError: true).asFuture();
|
||||||
cacheManager?.putStreamedFile(url, response);
|
|
||||||
await subscription.asFuture();
|
|
||||||
|
|
||||||
// Combine all chunks into a single buffer
|
|
||||||
bytes = Uint8List(totalLength);
|
bytes = Uint8List(totalLength);
|
||||||
for (final chunk in chunks) {
|
for (final chunk in chunks) {
|
||||||
bytes.setAll(offset, chunk);
|
bytes.setAll(offset, chunk);
|
||||||
|
|
@ -106,7 +119,7 @@ class RemoteImageRequest extends ImageRequest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return await ImmutableBuffer.fromUint8List(bytes);
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ImageInfo?> _loadCachedFile(
|
Future<ImageInfo?> _loadCachedFile(
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ class _ThumbnailState extends State<Thumbnail> with SingleTickerProviderStateMix
|
||||||
imageInfo.dispose();
|
imageInfo.dispose();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
_fadeController.value = 1.0;
|
||||||
setState(() {
|
setState(() {
|
||||||
_providerImage = imageInfo.image;
|
_providerImage = imageInfo.image;
|
||||||
});
|
});
|
||||||
|
|
@ -115,7 +115,7 @@ class _ThumbnailState extends State<Thumbnail> with SingleTickerProviderStateMix
|
||||||
final imageStream = _imageStream = imageProvider.resolve(ImageConfiguration.empty);
|
final imageStream = _imageStream = imageProvider.resolve(ImageConfiguration.empty);
|
||||||
final imageStreamListener = _imageStreamListener = ImageStreamListener(
|
final imageStreamListener = _imageStreamListener = ImageStreamListener(
|
||||||
(ImageInfo imageInfo, bool synchronousCall) {
|
(ImageInfo imageInfo, bool synchronousCall) {
|
||||||
_stopListeningToStream();
|
_stopListeningToThumbhashStream();
|
||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
imageInfo.dispose();
|
imageInfo.dispose();
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,21 @@ abstract class RemoteCacheManager extends CacheManager {
|
||||||
final file = await store.fileSystem.createFile(path);
|
final file = await store.fileSystem.createFile(path);
|
||||||
final sink = file.openWrite();
|
final sink = file.openWrite();
|
||||||
try {
|
try {
|
||||||
await source.pipe(sink);
|
await source.listen(sink.add, cancelOnError: true).asFuture();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
try {
|
||||||
await sink.close();
|
await sink.close();
|
||||||
|
await file.delete();
|
||||||
|
} catch (e) {
|
||||||
|
_log.severe('Failed to delete incomplete cache file: $e');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sink.flush();
|
||||||
|
await sink.close();
|
||||||
|
} catch (e) {
|
||||||
try {
|
try {
|
||||||
await file.delete();
|
await file.delete();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue