Skip to content

Commit d73259a

Browse files
mertalevollioddi
authored andcommitted
fix(mobile): caching thumbnails to disk (immich-app#21275)
1 parent 4cee659 commit d73259a

File tree

3 files changed

+51
-26
lines changed

3 files changed

+51
-26
lines changed

mobile/lib/infrastructure/loaders/remote_image_request.dart

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -65,48 +65,61 @@ class RemoteImageRequest extends ImageRequest {
6565
return null;
6666
}
6767

68-
// Handle unknown content length from reverse proxy
69-
final contentLength = response.contentLength;
68+
final cacheManager = this.cacheManager;
69+
final streamController = StreamController<List<int>>(sync: true);
70+
final Stream<List<int>> stream;
71+
cacheManager?.putStreamedFile(url, streamController.stream);
72+
stream = response.map((chunk) {
73+
if (_isCancelled) {
74+
throw StateError('Cancelled request');
75+
}
76+
if (cacheManager != null) {
77+
streamController.add(chunk);
78+
}
79+
return chunk;
80+
});
81+
82+
try {
83+
final Uint8List bytes = await _downloadBytes(stream, response.contentLength);
84+
streamController.close();
85+
return await ImmutableBuffer.fromUint8List(bytes);
86+
} catch (e) {
87+
streamController.addError(e);
88+
streamController.close();
89+
if (_isCancelled) {
90+
return null;
91+
}
92+
rethrow;
93+
}
94+
}
95+
96+
Future<Uint8List> _downloadBytes(Stream<List<int>> stream, int length) async {
7097
final Uint8List bytes;
7198
int offset = 0;
72-
73-
if (contentLength >= 0) {
99+
if (length > 0) {
74100
// Known content length - use pre-allocated buffer
75-
bytes = Uint8List(contentLength);
76-
final subscription = response.listen((List<int> chunk) {
77-
// this is important to break the response stream if the request is cancelled
78-
if (_isCancelled) {
79-
throw StateError('Cancelled request');
80-
}
101+
bytes = Uint8List(length);
102+
await stream.listen((chunk) {
81103
bytes.setAll(offset, chunk);
82104
offset += chunk.length;
83-
}, cancelOnError: true);
84-
cacheManager?.putStreamedFile(url, response);
85-
await subscription.asFuture();
105+
}, cancelOnError: true).asFuture();
86106
} else {
87107
// Unknown content length - collect chunks dynamically
88108
final chunks = <List<int>>[];
89109
int totalLength = 0;
90-
final subscription = response.listen((List<int> chunk) {
91-
// this is important to break the response stream if the request is cancelled
92-
if (_isCancelled) {
93-
throw StateError('Cancelled request');
94-
}
110+
await stream.listen((chunk) {
95111
chunks.add(chunk);
96112
totalLength += chunk.length;
97-
}, cancelOnError: true);
98-
cacheManager?.putStreamedFile(url, response);
99-
await subscription.asFuture();
113+
}, cancelOnError: true).asFuture();
100114

101-
// Combine all chunks into a single buffer
102115
bytes = Uint8List(totalLength);
103116
for (final chunk in chunks) {
104117
bytes.setAll(offset, chunk);
105118
offset += chunk.length;
106119
}
107120
}
108121

109-
return await ImmutableBuffer.fromUint8List(bytes);
122+
return bytes;
110123
}
111124

112125
Future<ImageInfo?> _loadCachedFile(

mobile/lib/presentation/widgets/images/thumbnail.widget.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ class _ThumbnailState extends State<Thumbnail> with SingleTickerProviderStateMix
9494
imageInfo.dispose();
9595
return;
9696
}
97-
97+
_fadeController.value = 1.0;
9898
setState(() {
9999
_providerImage = imageInfo.image;
100100
});
@@ -115,7 +115,7 @@ class _ThumbnailState extends State<Thumbnail> with SingleTickerProviderStateMix
115115
final imageStream = _imageStream = imageProvider.resolve(ImageConfiguration.empty);
116116
final imageStreamListener = _imageStreamListener = ImageStreamListener(
117117
(ImageInfo imageInfo, bool synchronousCall) {
118-
_stopListeningToStream();
118+
_stopListeningToThumbhashStream();
119119
if (!mounted) {
120120
imageInfo.dispose();
121121
return;

mobile/lib/providers/image/cache/remote_image_cache_manager.dart

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,21 @@ abstract class RemoteCacheManager extends CacheManager {
3838
final file = await store.fileSystem.createFile(path);
3939
final sink = file.openWrite();
4040
try {
41-
await source.pipe(sink);
41+
await source.listen(sink.add, cancelOnError: true).asFuture();
4242
} catch (e) {
43+
try {
44+
await sink.close();
45+
await file.delete();
46+
} catch (e) {
47+
_log.severe('Failed to delete incomplete cache file: $e');
48+
}
49+
return;
50+
}
51+
52+
try {
53+
await sink.flush();
4354
await sink.close();
55+
} catch (e) {
4456
try {
4557
await file.delete();
4658
} catch (e) {

0 commit comments

Comments
 (0)