Sert les couvertures depuis IndexedDB aussi en ligne, et libère les blob:

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mathieu
2026-08-20 23:09:17 +02:00
co-authored by Claude Opus 5
parent 70f6844a95
commit bdcf97a3e6
5 changed files with 145 additions and 26 deletions
+44 -17
View File
@@ -1,6 +1,7 @@
@using MaBibli.Client.Services
@inject CacheHorsLigne Cache
@inject EtatReseau Reseau
@implements IAsyncDisposable
@*
Couverture d'un livre, avec substitut.
@@ -20,10 +21,19 @@
seulement l'image. Sans ça, cliquer une vignette cassée agrandissait quand même une image
cassée (A4, IDEES.md).
Cache hors-ligne (A5, IDEES.md) : EN LIGNE, l'affichage utilise l'URL réseau telle quelle —
rien ne doit ralentir la voie rapide — et une mise en cache démarre en tâche de fond,
sans attendre son résultat, pour que la même couverture reste visible hors-ligne. HORS
LIGNE, on tente d'abord le cache : l'URL réseau échouerait de toute façon.
Cache des couvertures : le cache IndexedDB est consulté D'ABORD, en ligne comme hors ligne.
Une image déjà rangée sur l'appareil s'affiche sans aucune requête réseau — c'est ce qui rend
un rechargement instantané sur téléphone, là où covers.openlibrary.org impose deux
redirections avant d'aboutir sur archive.org. Le réseau n'est sollicité QUE si rien n'est en
cache, et la mise en cache démarre alors en tâche de fond.
⚠️ Ceci REMPLACE la règle d'origine (A5) « en ligne, rien ne doit ralentir la voie rapide »,
qui réservait le cache au hors-ligne. La lecture IndexedDB précède désormais l'affichage :
elle est locale et sans réseau, donc largement plus rapide que ce qu'elle remplace.
⚠️ Corollaire : l'URL d'objet doit être LIBÉRÉE quand le composant disparaît ou change
d'image (Dispose / changement d'URL). Un blob: retient son Blob en mémoire jusque-là, et il y
a maintenant une vignette par carte sur tous les écrans.
⚠️ L'allègement (redimensionner, convertir en WebP) n'est PAS fait ici : à mesurer avant
de s'y engager, comme pour l'AOT — voir IDEES.md.
*@
@@ -94,11 +104,11 @@
private bool ADesPixels => !string.IsNullOrWhiteSpace(Url);
/// <summary>
/// URL réellement posée en <c>src</c> : l'URL réseau en ligne (voie rapide, inchangée), le
/// blob mis en cache hors-ligne quand il existe, sinon l'URL réseau telle quelle (qui échoue
/// simplement, comme avant cette fonctionnalité).
/// URL réellement posée en <c>src</c> : le blob mis en cache dès qu'il existe — en ligne
/// comme hors ligne , sinon l'URL réseau telle quelle (qui échoue simplement hors-ligne,
/// exactement comme avant cette fonctionnalité).
/// </summary>
private string? UrlAffichee => !Reseau.EnLigne && _urlCachee is not null ? _urlCachee : Url;
private string? UrlAffichee => _urlCachee ?? Url;
private string Initiale =>
string.IsNullOrWhiteSpace(Titre) ? "?" : Titre.Trim()[..1].ToUpperInvariant();
@@ -131,21 +141,38 @@
}
_urlResoluePour = jeton;
_urlCachee = null;
await LibererAsync();
if (Reseau.EnLigne)
// Le cache d'abord, EN LIGNE COMME HORS LIGNE : une image déjà sur l'appareil s'affiche
// sans requête, sans redirection, et sans réveiller la radio du téléphone.
_urlCachee = await Cache.LireCouvertureCacheeAsync(Url!);
if (_urlCachee is null && Reseau.EnLigne)
{
// Tâche de fond, jamais attendue : la voie d'affichage en ligne ne doit rien à
// ce cache, elle reste aussi rapide qu'avant.
// Rien en cache : le réseau sert l'image (UrlAffichee retombe sur Url), et la mise
// en cache démarre en tâche de fond pour que le prochain chargement soit local.
// ⚠️ Jamais attendue : elle ne doit pas retarder un affichage qui va avoir lieu.
_ = Cache.MettreEnCacheCouvertureAsync(Url!);
}
else
{
_urlCachee = await Cache.LireCouvertureCacheeAsync(Url!);
StateHasChanged();
}
StateHasChanged();
}
/// <summary>Révoque l'URL d'objet en cours, s'il y en a une.</summary>
private async Task LibererAsync()
{
if (_urlCachee is null)
{
return;
}
var aLiberer = _urlCachee;
_urlCachee = null;
await Cache.LibererCouvertureAsync(aLiberer);
}
public async ValueTask DisposeAsync() => await LibererAsync();
protected override async Task OnAfterRenderAsync(bool premierRendu)
{
if (_aFocaliser)