loadPrintLogo function

Future<PrintLogo?> loadPrintLogo(
  1. String? url,
  2. SheetLogoSize size,
  3. KeckPaperSize paper, {
  4. PixelLoader? pixel,
  5. Duration timeout = const Duration(seconds: 3),
  6. Duration negativeCacheTtl = defaultNegativeCacheTtl,
})

Laedt das Logo unter url und rastert es in die Groesse, die das Blatt size und paper geben (logoDimensions/logoRaster) -- null ohne Adresse oder bei jedem Fehler (Netz, Decode, Pixelmass). pixel ersetzt den Standardweg ueber LogoService/decodePng (Tests, andere Quellen).

Ergebnisse werden je Adresse, Stufe und Papier zwischengespeichert (clearPrintLogoCache leert den Speicher). Ein Fehlschlag (null) wird nicht dauerhaft gemerkt, aber kurz: negativeCacheTtl (Vorgabe eine Minute) sperrt einen erneuten Versuch fuer denselben Schluessel. Ohne diese Sperre kostete eine kaputte Adresse jeden Bon erneut bis zu timeout am Tresen, ohne je zum Ziel zu kommen -- der Zustand hielt zwar nichts fest, aber der Preis war derselbe wie ohne jeden Speicher. Nach Ablauf der Sperre versucht der naechste Aufruf es wieder, damit eine inzwischen reparierte Adresse nicht laenger als noetig verborgen bleibt.

timeout deckt den ganzen Aufruf -- Abruf (Standardweg oder pixel), Decode und Raster -- mit einer einzigen Obergrenze, Vorgabe drei Sekunden (Hausregel wie im Druck-Kit der Browser-Kasse: das Logo ist Zierde, ein Druck darf nicht laenger darauf warten). LogoService.timeout bleibt daneben bestehen und deckt fuer sich nur den HTTP-Teil in _ausLogoService -- das ist keine zweite Frist fuer denselben Zweck, sondern eine eigene: der LogoService wird auch direkt genutzt (Bild-Anzeige in der App, ohne Bondruck) und braucht dort seine eigene Grenze. Treffen beide aufeinander (Standardweg, timeout hier kleiner oder gleich LogoService.timeout), gewinnt die kleinere: Future.timeout hier greift, sobald sie ablaeuft, egal wie weit der HTTP-Abruf innerhalb seiner eigenen Frist noch ist. Ein Timeout zaehlt wie jeder andere Fehlschlag fuer negativeCacheTtl: der Speichereintrag ist damit schon weg, ein Ergebnis, das erst danach eintrifft, findet keinen eigenen Eintrag mehr vor und schreibt keinen neuen; der naechste Aufruf innerhalb der Negativ-Frist bekommt sofort null, ohne selbst zu versuchen.

Implementation

Future<PrintLogo?> loadPrintLogo(
  String? url,
  SheetLogoSize size,
  KeckPaperSize paper, {
  PixelLoader? pixel,
  Duration timeout = const Duration(seconds: 3),
  Duration negativeCacheTtl = defaultNegativeCacheTtl,
}) {
  if (url == null || url.isEmpty) return Future.value(null);
  final schluessel = '$url|${size.code}|${paper.name}';
  final vorhanden = _speicher[schluessel];
  if (vorhanden != null) return vorhanden;

  final letzterFehlschlag = _negativCache[schluessel];
  if (letzterFehlschlag != null && DateTime.now().difference(letzterFehlschlag) < negativeCacheTtl) {
    return Future.value(null);
  }

  final abruf = _laden(url, size, paper, pixel).timeout(timeout, onTimeout: () => null);
  _speicher[schluessel] = abruf;
  abruf.then((logo) {
    // Nur den eigenen Eintrag entfernen: wurde der Speicher inzwischen
    // geleert oder laeuft schon ein neuerer Abruf, bleibt der stehen. Das
    // greift unveraendert auch nach einem Timeout, denn der Speicher haelt
    // genau dieses (mit `timeout` umhuellte) Future -- ein spaeter fertig
    // werdender roher Ladevorgang schreibt nirgends mehr hinein. Aus
    // demselben Grund merkt sich auch das Negativ-Gedaechtnis nur einen
    // Fehlschlag, der noch der aktuelle Stand ist -- ein laengst durch einen
    // neueren, erfolgreichen Abruf ersetzter Fehlschlag darf keine Sperre
    // fuer diesen setzen.
    if (logo == null && identical(_speicher[schluessel], abruf)) {
      _speicher.remove(schluessel);
      _negativCache[schluessel] = DateTime.now();
    }
  });
  return abruf;
}